From 369686c992097eedf2987ee3ba297135197d0ad0 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sat, 13 Jul 2019 02:41:34 -0700 Subject: [PATCH] Initial Commit --- .gitignore | 5 +- .gitmodules | 15 +- CMakeLists.txt | 101 + External/cpp-optparse | 1 + External/imgui | 1 + External/json-maker | 1 + External/tiny-json | 1 + LICENSE | 21 + Readme.md | 16 + Scripts/Threaded_Lockstep_Runner.py | 98 + Scripts/json_asm_parse.py | 229 ++ Source/CMakeLists.txt | 52 + Source/Common/ArgumentLoader.cpp | 150 ++ Source/Common/ArgumentLoader.h | 10 + Source/Common/CMakeLists.txt | 9 + Source/Common/Config.cpp | 115 + Source/Common/Config.h | 64 + Source/Common/MathUtils.h | 13 + Source/Common/StringConv.h | 42 + Source/Common/StringUtil.cpp | 19 + Source/Common/StringUtil.h | 9 + Source/CommonCore/CMakeLists.txt | 6 + Source/CommonCore/HostFactory.cpp | 296 +++ Source/CommonCore/HostFactory.h | 14 + Source/CommonCore/VMFactory.cpp | 293 +++ Source/CommonCore/VMFactory.h | 14 + Source/Core/CPU/HostCore/HostCore.cpp | 268 ++ Source/Core/CPU/HostCore/HostCore.h | 10 + Source/Tests/CMakeLists.txt | 55 + Source/Tests/ELFLoader.cpp | 102 + Source/Tests/HarnessHelpers.h | 598 +++++ Source/Tests/LockstepRunner.cpp | 738 ++++++ Source/Tests/TestHarness.cpp | 112 + Source/Tests/TestHarnessRunner.cpp | 96 + Source/Tests/TestSingleStepHardware.cpp | 113 + Source/Tests/UnitTestGenerator.cpp | 2440 +++++++++++++++++++ Source/Tools/CMakeLists.txt | 10 + Source/Tools/Debugger/CMakeLists.txt | 29 + Source/Tools/Debugger/Context.cpp | 90 + Source/Tools/Debugger/Context.h | 20 + Source/Tools/Debugger/DebuggerState.cpp | 149 ++ Source/Tools/Debugger/DebuggerState.h | 49 + Source/Tools/Debugger/Disassembler.cpp | 94 + Source/Tools/Debugger/Disassembler.h | 15 + Source/Tools/Debugger/FEXImGui.cpp | 153 ++ Source/Tools/Debugger/FEXImGui.h | 18 + Source/Tools/Debugger/GLUtils.cpp | 5 + Source/Tools/Debugger/GLUtils.h | 7 + Source/Tools/Debugger/IMGui.cpp | 1172 +++++++++ Source/Tools/Debugger/IMGui.h | 8 + Source/Tools/Debugger/IRLexer.cpp | 43 + Source/Tools/Debugger/IRLexer.h | 11 + Source/Tools/Debugger/Main.cpp | 207 ++ Source/Tools/Debugger/MainWindow.cpp | 1 + Source/Tools/Debugger/MainWindow.h | 3 + Source/Tools/Debugger/Util/DataRingBuffer.h | 55 + Source/Tools/Opt.cpp | 11 + docs/Debugger.md | 30 + docs/Diagram.svg | 3 + docs/VMCore.md | 17 + unittests/ASM/CALL.asm | 36 + unittests/ASM/CMakeLists.txt | 45 + unittests/ASM/JMP.asm | 55 + unittests/ASM/STOS.asm | 61 + unittests/ASM/jump.asm | 35 + unittests/ASM/lea.asm | 16 + unittests/ASM/mov.asm | 36 + unittests/ASM/movups.asm | 66 + unittests/ASM/movzx.asm | 23 + unittests/ASM/mul.asm | 17 + unittests/ASM/simple_loop.asm | 42 + unittests/CMakeLists.txt | 1 + unittests/Example.asm | 64 + 73 files changed, 8820 insertions(+), 4 deletions(-) create mode 100644 CMakeLists.txt create mode 160000 External/cpp-optparse create mode 160000 External/imgui create mode 160000 External/json-maker create mode 160000 External/tiny-json create mode 100644 LICENSE create mode 100644 Readme.md create mode 100755 Scripts/Threaded_Lockstep_Runner.py create mode 100644 Scripts/json_asm_parse.py create mode 100644 Source/CMakeLists.txt create mode 100644 Source/Common/ArgumentLoader.cpp create mode 100644 Source/Common/ArgumentLoader.h create mode 100644 Source/Common/CMakeLists.txt create mode 100644 Source/Common/Config.cpp create mode 100644 Source/Common/Config.h create mode 100644 Source/Common/MathUtils.h create mode 100644 Source/Common/StringConv.h create mode 100644 Source/Common/StringUtil.cpp create mode 100644 Source/Common/StringUtil.h create mode 100644 Source/CommonCore/CMakeLists.txt create mode 100644 Source/CommonCore/HostFactory.cpp create mode 100644 Source/CommonCore/HostFactory.h create mode 100644 Source/CommonCore/VMFactory.cpp create mode 100644 Source/CommonCore/VMFactory.h create mode 100644 Source/Core/CPU/HostCore/HostCore.cpp create mode 100644 Source/Core/CPU/HostCore/HostCore.h create mode 100644 Source/Tests/CMakeLists.txt create mode 100644 Source/Tests/ELFLoader.cpp create mode 100644 Source/Tests/HarnessHelpers.h create mode 100644 Source/Tests/LockstepRunner.cpp create mode 100644 Source/Tests/TestHarness.cpp create mode 100644 Source/Tests/TestHarnessRunner.cpp create mode 100644 Source/Tests/TestSingleStepHardware.cpp create mode 100644 Source/Tests/UnitTestGenerator.cpp create mode 100644 Source/Tools/CMakeLists.txt create mode 100644 Source/Tools/Debugger/CMakeLists.txt create mode 100644 Source/Tools/Debugger/Context.cpp create mode 100644 Source/Tools/Debugger/Context.h create mode 100644 Source/Tools/Debugger/DebuggerState.cpp create mode 100644 Source/Tools/Debugger/DebuggerState.h create mode 100644 Source/Tools/Debugger/Disassembler.cpp create mode 100644 Source/Tools/Debugger/Disassembler.h create mode 100644 Source/Tools/Debugger/FEXImGui.cpp create mode 100644 Source/Tools/Debugger/FEXImGui.h create mode 100644 Source/Tools/Debugger/GLUtils.cpp create mode 100644 Source/Tools/Debugger/GLUtils.h create mode 100644 Source/Tools/Debugger/IMGui.cpp create mode 100644 Source/Tools/Debugger/IMGui.h create mode 100644 Source/Tools/Debugger/IRLexer.cpp create mode 100644 Source/Tools/Debugger/IRLexer.h create mode 100644 Source/Tools/Debugger/Main.cpp create mode 100644 Source/Tools/Debugger/MainWindow.cpp create mode 100644 Source/Tools/Debugger/MainWindow.h create mode 100644 Source/Tools/Debugger/Util/DataRingBuffer.h create mode 100644 Source/Tools/Opt.cpp create mode 100644 docs/Debugger.md create mode 100644 docs/Diagram.svg create mode 100644 docs/VMCore.md create mode 100644 unittests/ASM/CALL.asm create mode 100644 unittests/ASM/CMakeLists.txt create mode 100644 unittests/ASM/JMP.asm create mode 100644 unittests/ASM/STOS.asm create mode 100644 unittests/ASM/jump.asm create mode 100644 unittests/ASM/lea.asm create mode 100644 unittests/ASM/mov.asm create mode 100644 unittests/ASM/movups.asm create mode 100644 unittests/ASM/movzx.asm create mode 100644 unittests/ASM/mul.asm create mode 100644 unittests/ASM/simple_loop.asm create mode 100644 unittests/CMakeLists.txt create mode 100644 unittests/Example.asm diff --git a/.gitignore b/.gitignore index 1097564e55..37df0a9b58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ - compile_commands.json vim_rc +Config.json + +[Bb]uild*/ +.vscode/ diff --git a/.gitmodules b/.gitmodules index 071dc371be..936551bbb9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,15 @@ -[submodule "External/SonicUtils"] - path = External/SonicUtils - url = https://github.com/Sonicadvance1/SonicUtils.git [submodule "External/vixl"] path = External/vixl url = https://github.com/Sonicadvance1/vixl.git +[submodule "External/cpp-optparse"] + path = External/cpp-optparse + url = https://github.com/Sonicadvance1/cpp-optparse +[submodule "External/imgui"] + path = External/imgui + url = https://github.com/Sonicadvance1/imgui.git +[submodule "External/json-maker"] + path = External/json-maker + url = https://github.com/Sonicadvance1/json-maker.git +[submodule "External/tiny-json"] + path = External/tiny-json + url = https://github.com/Sonicadvance1/tiny-json.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..ada594224f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.10) +project(FEX) + +option(BUILD_TESTS "Build unit tests to ensure sanity" TRUE) +option(ENABLE_CLANG_FORMAT "Run clang format over the source" FALSE) +option(ENABLE_LTO "Enable LTO with compilation" TRUE) +option(ENABLE_XRAY "Enable building with LLVM X-Ray" FALSE) +option(ENABLE_LLD "Enable linking with LLD" FALSE) +option(ENABLE_ASAN "Enables Clang ASAN" FALSE) +option(ENABLE_TSAN "Enables Clang TSAN" FALSE) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Bin) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +if (ENABLE_LTO) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +if (ENABLE_XRAY) + add_compile_options(-fxray-instrument) + link_libraries(-fxray-instrument) +endif() + +if (ENABLE_LLD) + link_libraries(-fuse-ld=lld) +endif() + +if (ENABLE_ASAN) + add_compile_options(-fno-omit-frame-pointer -fsanitize=address) + link_libraries(-fno-omit-frame-pointer -fsanitize=address) +endif() + +if (ENABLE_TSAN) + add_compile_options(-fno-omit-frame-pointer -fsanitize=thread) + link_libraries(-fno-omit-frame-pointer -fsanitize=thread) +endif() + +set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer") +set (CMAKE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_LINKER_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer") + +set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fomit-frame-pointer") +set (CMAKE_LINKER_FLAGS_RELEASE "${CMAKE_LINKER_FLAGS_RELEASE} -fomit-frame-pointer") + +add_definitions(-Wno-trigraphs) + +# add_subdirectory(External/SonicUtils/) +include_directories(External/SonicUtils/) + +add_subdirectory(External/cpp-optparse/) +include_directories(External/cpp-optparse/) + +add_subdirectory(External/imgui/) +include_directories(External/imgui/) + +add_subdirectory(External/json-maker/) +include_directories(External/json-maker/) + +add_subdirectory(External/tiny-json/) +include_directories(External/tiny-json/) + +include_directories(Source/) +include_directories("${CMAKE_BINARY_DIR}/Source/") + +add_subdirectory(External/FEXCore) + +find_package(LLVM CONFIG QUIET) +if(LLVM_FOUND AND TARGET LLVM) + message(STATUS "LLVM found!") + include_directories(${LLVM_INCLUDE_DIRS}) +endif() + +include(CheckCXXCompilerFlag) + +# Add in diagnostic colours if the option is available. +# Ninja code generator will kill colours if this isn't here +check_cxx_compiler_flag(-fdiagnostics-color=always GCC_COLOR) +check_cxx_compiler_flag(-fcolor-diagnostics CLANG_COLOR) + +if (GCC_COLOR) + add_compile_options(-fdiagnostics-color=always) +endif() +if (CLANG_COLOR) + add_compile_options(-fcolor-diagnostics) +endif() + +check_cxx_compiler_flag("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) +if(COMPILER_SUPPORTS_MARCH_NATIVE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") +endif() + +add_compile_options(-Wall) + +add_subdirectory(Source/) + +if (BUILD_TESTS) + enable_testing() + message(STATUS "Unit tests are enabled") + add_subdirectory(unittests/) +endif() diff --git a/External/cpp-optparse b/External/cpp-optparse new file mode 160000 index 0000000000..5d46ee5bb5 --- /dev/null +++ b/External/cpp-optparse @@ -0,0 +1 @@ +Subproject commit 5d46ee5bb5c7b5e8988c719aa2b45119df6c5092 diff --git a/External/imgui b/External/imgui new file mode 160000 index 0000000000..6ad37300cc --- /dev/null +++ b/External/imgui @@ -0,0 +1 @@ +Subproject commit 6ad37300cc85c8b195d87055875244585b531cc6 diff --git a/External/json-maker b/External/json-maker new file mode 160000 index 0000000000..8ecb8ecc34 --- /dev/null +++ b/External/json-maker @@ -0,0 +1 @@ +Subproject commit 8ecb8ecc348bf88c592fac808c03efb342f69e0a diff --git a/External/tiny-json b/External/tiny-json new file mode 160000 index 0000000000..9d09127f87 --- /dev/null +++ b/External/tiny-json @@ -0,0 +1 @@ +Subproject commit 9d09127f87ea6a128fb17d1ffd0b444517343f1c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..cdb4f90b34 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Ryan Houdek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000000..5f8580149b --- /dev/null +++ b/Readme.md @@ -0,0 +1,16 @@ +# FEX - Fast x86 emulation frontend +This is the frontend application and tooling used for development and debugging of the FEXCore library. + +### Dependencies +* [SonicUtils](https://github.com/Sonicadvance1/SonicUtils) +* FEXCore +* cpp-optparse +* imgui +* json-maker +* tiny-json +* boost interprocess (sadly) +* A C++17 compliant compiler (There are assumptions made about using Clang and LTO) +* clang-tidy if you want the code cleaned up +* cmake + +![FEX diagram](docs/Diagram.svg) diff --git a/Scripts/Threaded_Lockstep_Runner.py b/Scripts/Threaded_Lockstep_Runner.py new file mode 100755 index 0000000000..23993928d1 --- /dev/null +++ b/Scripts/Threaded_Lockstep_Runner.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 +from enum import Flag +import json +import os +import struct +import sys +import glob +from threading import Thread +import subprocess +import time +import multiprocessing + +if sys.version_info[0] < 3: + raise Exception("Python 3 or a more recent version is required.") + +if (len(sys.argv) < 3): + sys.exit("We need two arguments. Location of LockStepRunner and folder containing the tests") + +# Remove our SHM regions if they still exist +SHM_Files = glob.glob("/dev/shm/*_Lockstep") +for file in SHM_Files: + os.remove(file) + +UnitTests = sorted(glob.glob(sys.argv[2] + "*")) +UnitTestsSize = len(UnitTests) +Threads = [None] * UnitTestsSize +Results = [None] * UnitTestsSize +ThreadResults = [[None] * 2] * UnitTestsSize +MaxFileNameStringLen = 0 + +def Threaded_Runner(Args, ID, Client): + Log = open("Log_" + str(ID) + "_" + str(Client), "w") + Log.write("Args: %s\n" % " ".join(Args)) + Log.flush() + Process = subprocess.Popen(Args, stdout=Log, stderr=Log) + Process.wait() + Log.flush() + ThreadResults[ID][Client] = Process.returncode + +def Threaded_Manager(Runner, ID, File): + ServerArgs = ["catchsegv", Runner, "-c", "vm", "-n", "1", "-I", "R" + str(ID), File] + ClientArgs = ["catchsegv", Runner, "-c", "vm", "-n", "1", "-I", "R" + str(ID), "-C"] + + ServerThread = Thread(target = Threaded_Runner, args = (ServerArgs, ID, 0)) + ClientThread = Thread(target = Threaded_Runner, args = (ClientArgs, ID, 1)) + + ServerThread.start() + ClientThread.start() + + ClientThread.join() + ServerThread.join() + + # The server is the one we should listen to for results + if (ThreadResults[ID][1] != 0 and ThreadResults[ID][0] == 0): + # If the client died for some reason but server thought we were fine then take client data + Results[ID] = ThreadResults[ID][1] + else: + # Else just take the server data + Results[ID] = ThreadResults[ID][0] + + DupLen = MaxFileNameStringLen - len(UnitTests[ID]) + + if (Results[ID] == 0): + print("\t'%s'%s - PASSED ID: %d - 0" % (UnitTests[ID], " "*DupLen, ID)) + else: + print("\t'%s'%s - FAILED ID: %d - %s" % (UnitTests[ID], " "*DupLen, ID, hex(Results[ID]))) + +RunnerSlot = 0 +MaxRunnerSlots = min(32, multiprocessing.cpu_count() / 2) +RunnerSlots = [None] * MaxRunnerSlots +for RunnerID in range(UnitTestsSize): + File = UnitTests[RunnerID] + print("'%s' Running Test" % File) + MaxFileNameStringLen = max(MaxFileNameStringLen, len(File)) + Threads[RunnerID] = Thread(target = Threaded_Manager, args = (sys.argv[1], RunnerID, File)) + Threads[RunnerID].start() + if (MaxRunnerSlots != 0): + RunnerSlots[RunnerSlot] = Threads[RunnerID] + RunnerSlot += 1 + if (RunnerSlot == MaxRunnerSlots): + for i in range(MaxRunnerSlots): + RunnerSlots[i].join() + RunnerSlot = 0 + +for i in range(UnitTestsSize): + Threads[i].join() + +print("====== PASSED RESULTS ======") +for i in range(UnitTestsSize): + DupLen = MaxFileNameStringLen - len(UnitTests[i]) + if (Results[i] == 0): + print("\t'%s'%s - PASSED ID: %d - 0" % (UnitTests[i], " "*DupLen, i)) + +print("====== FAILED RESULTS ======") +for i in range(UnitTestsSize): + DupLen = MaxFileNameStringLen - len(UnitTests[i]) + if (Results[i] != 0): + print("\t'%s'%s - FAILED ID: %d - %s" % (UnitTests[i], " "*DupLen, i, hex(Results[i]))) diff --git a/Scripts/json_asm_parse.py b/Scripts/json_asm_parse.py new file mode 100644 index 0000000000..949310b80d --- /dev/null +++ b/Scripts/json_asm_parse.py @@ -0,0 +1,229 @@ +from enum import Flag +import json +import struct +import sys + +class Regs(Flag): + REG_NONE = 0 + REG_RIP = (1 << 0) + REG_RAX = (1 << 1) + REG_RBX = (1 << 2) + REG_RCX = (1 << 3) + REG_RDX = (1 << 4) + REG_RSI = (1 << 5) + REG_RDI = (1 << 6) + REG_RBP = (1 << 7) + REG_RSP = (1 << 8) + REG_R8 = (1 << 9) + REG_R9 = (1 << 10) + REG_R10 = (1 << 11) + REG_R11 = (1 << 12) + REG_R12 = (1 << 13) + REG_R13 = (1 << 14) + REG_R14 = (1 << 15) + REG_R15 = (1 << 16) + REG_XMM0 = (1 << 17) + REG_XMM1 = (1 << 18) + REG_XMM2 = (1 << 19) + REG_XMM3 = (1 << 20) + REG_XMM4 = (1 << 21) + REG_XMM5 = (1 << 22) + REG_XMM6 = (1 << 23) + REG_XMM7 = (1 << 24) + REG_XMM8 = (1 << 25) + REG_XMM9 = (1 << 26) + REG_XMM10 = (1 << 27) + REG_XMM11 = (1 << 28) + REG_XMM12 = (1 << 29) + REG_XMM13 = (1 << 30) + REG_XMM14 = (1 << 31) + REG_XMM15 = (1 << 32) + REG_GS = (1 << 33) + REG_FS = (1 << 34) + REG_FLAGS = (1 << 35) + REG_ALL = (1 << 36) - 1 + REG_INVALID = (1 << 36) + +class ABI(Flag) : + ABI_SYSTEMV = 0 + ABI_WIN64 = 1 + ABI_NONE = 2 + +RegStringLookup = { + "NONE": Regs.REG_NONE, + "RAX": Regs.REG_RAX, + "RIP": Regs.REG_RIP, + "RBX": Regs.REG_RBX, + "RCX": Regs.REG_RCX, + "RDX": Regs.REG_RDX, + "RSI": Regs.REG_RSI, + "RDI": Regs.REG_RDI, + "RBP": Regs.REG_RBP, + "RSP": Regs.REG_RSP, + "R8": Regs.REG_R8, + "R9": Regs.REG_R9, + "R10": Regs.REG_R10, + "R11": Regs.REG_R11, + "R12": Regs.REG_R12, + "R13": Regs.REG_R13, + "R14": Regs.REG_R14, + "R15": Regs.REG_R15, + "XMM0": Regs.REG_XMM0, + "XMM1": Regs.REG_XMM1, + "XMM2": Regs.REG_XMM2, + "XMM3": Regs.REG_XMM3, + "XMM4": Regs.REG_XMM4, + "XMM5": Regs.REG_XMM5, + "XMM6": Regs.REG_XMM6, + "XMM7": Regs.REG_XMM7, + "XMM8": Regs.REG_XMM8, + "XMM9": Regs.REG_XMM9, + "XMM10": Regs.REG_XMM10, + "XMM11": Regs.REG_XMM11, + "XMM12": Regs.REG_XMM12, + "XMM13": Regs.REG_XMM13, + "XMM14": Regs.REG_XMM14, + "XMM15": Regs.REG_XMM15, + "GS": Regs.REG_GS, + "FS": Regs.REG_FS, + "FLAGS": Regs.REG_FLAGS, + "ALL": Regs.REG_ALL, +} + +ABIStringLookup = { + "SYSTEMV": ABI.ABI_SYSTEMV, + "WIN64": ABI.ABI_WIN64, + "NONE": ABI.ABI_NONE, +} + +if (len(sys.argv) < 3): + sys.exit() + +output_file = sys.argv[2] +asm_file = open(sys.argv[1], "r") +asm_text = asm_file.read() +asm_file.close() + +# Default options +OptionMatch = Regs.REG_INVALID +OptionIgnore = Regs.REG_NONE +OptionABI = ABI.ABI_SYSTEMV +OptionStackSize = 4096 +OptionEntryPoint = 1 +OptionRegData = {} +OptionMemoryRegions = {} + +json_text = asm_text.split("%ifdef CONFIG") +if (len(json_text) > 1): + json_text = json_text[1].split("%endif") + if (len(json_text) > 1): + json_text = json_text[0].strip() + + json_object = json.loads(json_text) + json_object = {k.upper(): v for k, v in json_object.items()} + + # Begin parsing the JSON + if ("MATCH" in json_object): + data = json_object["MATCH"] + if (type(data) is str): + data = [data] + + for data_val in data: + data_val = data_val.upper() + if not (data_val in RegStringLookup): + sys.exit("Invalid Match register option") + if (OptionMatch == Regs.REG_INVALID): + OptionMatch = Regs.REG_NONE + RegOption = RegStringLookup[data_val] + OptionMatch = OptionMatch | RegOption + + if ("IGNORE" in json_object): + data = json_object["IGNORE"] + if (type(data) is str): + data = [data] + + for data_val in data: + data_val = data_val.upper() + if not (data_val in RegStringLookup): + sys.exit("Invalid Ignore register option") + if (OptionMatch == Regs.REG_INVALID): + OptionMatch = Regs.REG_NONE + RegOption = RegStringLookup[data_val] + OptionIgnore = OptionIgnore | RegOption + + if ("ABI" in json_object): + data = json_object["ABI"] + data = data.upper() + if not (data in ABIStringLookup): + sys.exit("Invalid ABI") + OptionABI = ABIStringLookup[data] + + if ("STACKSIZE" in json_object): + data = json_object["STACKSIZE"] + OptionStackSize = int(data, 0) + + if ("ENTRYPOINT" in json_object): + data = json_object["ENTRYPOINT"] + data = int(data, 0) + if (data == 0): + sys.exit("Invalid entrypoint of 0") + OptionEntryPoint = data + + if ("MEMORYREGIONS" in json_object): + data = json_object["MEMORYREGIONS"] + if not (type(data) is dict): + sys.exit("RegData value must be list of key:value pairs") + for data_key, data_val in data.items(): + OptionMemoryRegions[int(data_key, 0)] = int(data_val, 0); + + if ("REGDATA" in json_object): + data = json_object["REGDATA"] + if not (type(data) is dict): + sys.exit("RegData value must be list of key:value pairs") + for data_key, data_val in data.items(): + data_key = data_key.upper() + if not (data_key in RegStringLookup): + sys.exit("Invalid RegData register option") + + data_key_index = RegStringLookup[data_key] + data_key_values = [] + + # Create a list of values for this register as an integer + if (type(data_val) is list): + for data_key_value in data_val: + data_key_values.append(int(data_key_value, 0)) + else: + data_key_values.append(int(data_val, 0)) + OptionRegData[data_key_index] = data_key_values + + # If Match option wasn't touched then set it to the default + if (OptionMatch == Regs.REG_INVALID): + OptionMatch = Regs.REG_NONE + + config_file = open(output_file, "wb") + config_file.write(struct.pack('Q', OptionMatch.value)) + config_file.write(struct.pack('Q', OptionIgnore.value)) + config_file.write(struct.pack('Q', OptionStackSize)) + config_file.write(struct.pack('Q', OptionEntryPoint)) + config_file.write(struct.pack('I', OptionABI.value)) + + # Number of memory regions + config_file.write(struct.pack('I', len(OptionMemoryRegions))) + + # Number of register values + config_file.write(struct.pack('I', len(OptionRegData))) + + # Print number of memory regions + for reg_key, reg_val in OptionMemoryRegions.items(): + config_file.write(struct.pack('Q', reg_key)) + config_file.write(struct.pack('Q', reg_val)) + + # Print Register values + for reg_key, reg_val in OptionRegData.items(): + config_file.write(struct.pack('I', len(reg_val))) + config_file.write(struct.pack('Q', reg_key.value)) + for reg_vals in reg_val: + config_file.write(struct.pack('Q', reg_vals)) + + config_file.close() + diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt new file mode 100644 index 0000000000..f1f1b6418d --- /dev/null +++ b/Source/CMakeLists.txt @@ -0,0 +1,52 @@ +if (ENABLE_CLANG_FORMAT) + find_program(CLANG_TIDY_EXE "clang-tidy") + set(CLANG_TIDY_FLAGS + "-checks=*" + "-fuchsia*" + "-bugprone-macro-parentheses" + "-clang-analyzer-core.*" + "-cppcoreguidelines-pro-type-*" + "-cppcoreguidelines-pro-bounds-array-to-pointer-decay" + "-cppcoreguidelines-pro-bounds-pointer-arithmetic" + "-cppcoreguidelines-avoid-c-arrays" + "-cppcoreguidelines-avoid-magic-numbers" + "-cppcoreguidelines-pro-bounds-constant-array-index" + "-cppcoreguidelines-no-malloc" + "-cppcoreguidelines-special-member-functions" + "-cppcoreguidelines-owning-memory" + "-cppcoreguidelines-macro-usage" + "-cppcoreguidelines-avoid-goto" + "-google-readability-function-size" + "-google-readability-namespace-comments" + "-google-readability-braces-around-statements" + "-google-build-using-namespace" + "-hicpp-*" + "-llvm-namespace-comment" + "-llvm-include-order" # Messes up with case sensitivity + "-misc-unused-parameters" + "-modernize-loop-convert" + "-modernize-use-auto" + "-modernize-avoid-c-arrays" + "-modernize-use-nodiscard" + "readability-*" + "-readability-function-size" + "-readability-implicit-bool-conversion" + "-readability-braces-around-statements" + "-readability-else-after-return" + "-readability-magic-numbers" + "-readability-named-parameter" + "-readability-uppercase-literal-suffix" + "-cert-err34-c" + "-cert-err58-cpp" + "-bugprone-exception-escape" + ) + string(REPLACE ";" "," CLANG_TIDY_FLAGS "${CLANG_TIDY_FLAGS}") + + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} "${CLANG_TIDY_FLAGS}") +endif() + + +add_subdirectory(Common/) +add_subdirectory(CommonCore/) +add_subdirectory(Tests/) +add_subdirectory(Tools/) diff --git a/Source/Common/ArgumentLoader.cpp b/Source/Common/ArgumentLoader.cpp new file mode 100644 index 0000000000..a4bf284dc7 --- /dev/null +++ b/Source/Common/ArgumentLoader.cpp @@ -0,0 +1,150 @@ +#include "Common/Config.h" + +#include "OptionParser.h" + +namespace FEX::ArgLoader { + std::vector RemainingArgs; + + void Load(int argc, char **argv) { + + optparse::OptionParser Parser{}; + optparse::OptionGroup CPUGroup(Parser, "CPU Core options"); + optparse::OptionGroup TestGroup(Parser, "Test Harness options"); + + { + CPUGroup.add_option("-c", "--core") + .dest("Core") + .help("Which CPU core to use") + .choices({"irint", "irjit", "llvm", "host", "vm"}) + .set_default("irint"); + + Parser.set_defaults("Break", "0"); + Parser.set_defaults("Multiblock", "0"); + + + CPUGroup.add_option("-b", "--break") + .dest("Break") + .action("store_true") + .help("Break when op dispatcher doesn't understand instruction"); + CPUGroup.add_option("--no-break") + .dest("Break") + .action("store_false") + .help("Break when op dispatcher doesn't understand instruction"); + + CPUGroup.add_option("-s", "--single-step") + .dest("SingleStep") + .action("store_true") + .help("Single Step config"); + + CPUGroup.add_option("-n", "--max-inst") + .dest("MaxInst") + .help("Maximum number of instructions to stick in a block") + .set_default(~0U); + CPUGroup.add_option("-m", "--multiblock") + .dest("Multiblock") + .action("store_true") + .help("Enable Multiblock code compilation"); + CPUGroup.add_option("--no-multiblock") + .dest("Multiblock") + .action("store_false") + .help("Enable Multiblock code compilation"); + + Parser.add_option_group(CPUGroup); + } + { + TestGroup.add_option("-g", "--dump-gprs") + .dest("DumpGPRs") + .action("store_true") + .help("When Test Harness ends, print GPR state") + .set_default(false); + + TestGroup.add_option("-C", "--ipc-client") + .dest("IPCClient") + .action("store_true") + .help("If the lockstep runner is a client or server") + .set_default(false); + + TestGroup.add_option("-I", "--ID") + .dest("IPCID") + .help("Sets an ID that is prepended to IPC names. For multiple runners") + .set_default("0"); + + TestGroup.add_option("-e", "--elf") + .dest("ELFType") + .action("store_true") + .help("Lockstep runner should load argument as ELF") + .set_default(false); + + Parser.add_option_group(TestGroup); + } + optparse::Values Options = Parser.parse_args(argc, argv); + + { + if (Options.is_set_by_user("Core")) { + auto Core = Options["Core"]; + if (Core == "irint") + Config::Add("Core", "0"); + else if (Core == "irjit") + Config::Add("Core", "1"); + else if (Core == "llvm") + Config::Add("Core", "2"); + else if (Core == "host") + Config::Add("Core", "3"); + else if (Core == "vm") + Config::Add("Core", "4"); + } + + if (Options.is_set_by_user("Break")) { + bool Break = Options.get("Break"); + Config::Add("Break", std::to_string(Break)); + } + + if (Options.is_set_by_user("SingleStep")) { + bool SingleStep = Options.get("SingleStep"); + Config::Add("SingleStep", std::to_string(SingleStep)); + + // Single stepping also enforces single instruction size blocks + Config::Add("MaxInst", std::to_string(1u)); + } + else { + if (Options.is_set_by_user("MaxInst")) { + uint32_t MaxInst = Options.get("MaxInst"); + Config::Add("MaxInst", std::to_string(MaxInst)); + } + } + + if (Options.is_set_by_user("Multiblock")) { + bool Multiblock = Options.get("Multiblock"); + Config::Add("Multiblock", std::to_string(Multiblock)); + } + + } + + { + if (Options.is_set_by_user("DumpGPRs")) { + bool DumpGPRs = Options.get("DumpGPRs"); + Config::Add("DumpGPRs", std::to_string(DumpGPRs)); + } + + if (Options.is_set_by_user("IPCClient")) { + bool IPCClient = Options.get("IPCClient"); + Config::Add("IPCClient", std::to_string(IPCClient)); + } + + if (Options.is_set_by_user("ELFType")) { + bool ELFType = Options.get("ELFType"); + Config::Add("ELFType", std::to_string(ELFType)); + } + if (Options.is_set_by_user("IPCID")) { + const char* Value = Options.get("IPCID"); + Config::Add("IPCID", Value); + } + + } + RemainingArgs = Parser.args(); + } + + std::vector Get() { + return RemainingArgs; + } +} diff --git a/Source/Common/ArgumentLoader.h b/Source/Common/ArgumentLoader.h new file mode 100644 index 0000000000..81651343b0 --- /dev/null +++ b/Source/Common/ArgumentLoader.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace FEX::ArgLoader { + void Load(int argc, char **argv); + + std::vector Get(); +} diff --git a/Source/Common/CMakeLists.txt b/Source/Common/CMakeLists.txt new file mode 100644 index 0000000000..ae76753c3b --- /dev/null +++ b/Source/Common/CMakeLists.txt @@ -0,0 +1,9 @@ +set(NAME Common) +set(SRCS + ArgumentLoader.cpp + Config.cpp + StringUtil.cpp) + +add_library(${NAME} STATIC ${SRCS}) +target_link_libraries(${NAME} cpp-optparse tiny-json json-maker) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/cpp-optparse/) diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp new file mode 100644 index 0000000000..d7799f2505 --- /dev/null +++ b/Source/Common/Config.cpp @@ -0,0 +1,115 @@ +#include "Common/Config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FEX::Config { + std::unordered_map ConfigMap; + + struct JsonAllocator { + jsonPool_t PoolObject; + std::unique_ptr> json_objects; + }; + + json_t* PoolInit(jsonPool_t* Pool) { + JsonAllocator* alloc = json_containerOf(Pool, JsonAllocator, PoolObject); + alloc->json_objects = std::make_unique>(); + return &*alloc->json_objects->emplace(alloc->json_objects->end()); + } + + json_t* PoolAlloc(jsonPool_t* Pool) { + JsonAllocator* alloc = json_containerOf(Pool, JsonAllocator, PoolObject); + return &*alloc->json_objects->emplace(alloc->json_objects->end()); + } + + void Init() { + JsonAllocator Pool { + .PoolObject = { + .init = PoolInit, + .alloc = PoolAlloc, + }, + }; + + std::fstream ConfigFile; + std::vector Data; + ConfigFile.open("Config.json", std::fstream::in); + + if (ConfigFile.is_open()) { + ConfigFile.seekg(0, std::fstream::end); + size_t FileSize = ConfigFile.tellg(); + ConfigFile.seekg(0, std::fstream::beg); + + if (FileSize > 0) { + Data.resize(FileSize); + ConfigFile.read(&Data.at(0), FileSize); + ConfigFile.close(); + + json_t const *json = json_createWithPool(&Data.at(0), &Pool.PoolObject); + json_t const* ConfigList = json_getProperty(json, "Config"); + + for (json_t const* ConfigItem = json_getChild(ConfigList); + ConfigItem != nullptr; + ConfigItem = json_getSibling(ConfigItem)) { + const char* ConfigName = json_getName(ConfigItem); + const char* ConfigString = json_getValue(ConfigItem); + + FEX::Config::Add(ConfigName, ConfigString); + } + } + } + } + + void Shutdown() { + char Buffer[4096]; + char *Dest; + Dest = json_objOpen(Buffer, nullptr); + Dest = json_objOpen(Dest, "Config"); + for (auto &it : ConfigMap) { + Dest = json_str(Dest, it.first.data(), it.second.c_str()); + } + Dest = json_objClose(Dest); + Dest = json_objClose(Dest); + json_end(Dest); + + std::fstream ConfigFile; + ConfigFile.open("Config.json", std::fstream::out); + + if (ConfigFile.is_open()) { + ConfigFile.write(Buffer, strlen(Buffer)); + ConfigFile.close(); + } + + ConfigMap.clear(); + } + + void Add(std::string const &Key, std::string_view const Value) { + ConfigMap[Key] = Value; + } + + bool Exists(std::string const &Key) { + return ConfigMap.find(Key) != ConfigMap.end(); + } + + std::string_view Get(std::string const &Key) { + auto Value = ConfigMap.find(Key); + if (Value == ConfigMap.end()) + assert(0 && "Not a real config value"); + std::string_view ValueView = Value->second; + return ValueView; + } + + std::string_view GetIfExists(std::string const &Key, std::string_view const Default) { + auto Value = ConfigMap.find(Key); + if (Value == ConfigMap.end()) + return Default; + + std::string_view ValueView = Value->second; + return ValueView; + } +} diff --git a/Source/Common/Config.h b/Source/Common/Config.h new file mode 100644 index 0000000000..f19c4efb32 --- /dev/null +++ b/Source/Common/Config.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Common/StringConv.h" + +#include +#include +#include + +/** + * @brief This is a singleton for storing global configuration state + */ +namespace FEX::Config { + void Init(); + void Shutdown(); + + void Add(std::string const &Key, std::string_view const Value); + bool Exists(std::string const &Key); + std::string_view Get(std::string const &Key); + std::string_view GetIfExists(std::string const &Key, std::string_view const Default = ""); + + template + T Get(std::string const &Key) { + T Value; + if (!FEX::StrConv::Conv(Get(Key), &Value)) { + assert(0 && "Attempted to convert invalid value"); + } + return Value; + } + + template + T GetIfExists(std::string const &Key, T Default) { + T Value; + if (Exists(Key) && FEX::StrConv::Conv(FEX::Config::Get(Key), &Value)) { + return Value; + } + else { + return Default; + } + } + + template + class Value { + public: + Value(std::string const &key, T Default) + : Key {key} + { + ValueData = GetFromConfig(Key, Default); + } + void Set(T NewValue) { + ValueData = NewValue; + FEX::Config::Add(Key, std::to_string(NewValue)); + } + + T operator()() { return ValueData; } + + private: + std::string const Key; + T ValueData; + T GetFromConfig(std::string const &Key, T Default) { + return FEX::Config::GetIfExists(Key, Default); + } + }; + +} diff --git a/Source/Common/MathUtils.h b/Source/Common/MathUtils.h new file mode 100644 index 0000000000..f82a9c3944 --- /dev/null +++ b/Source/Common/MathUtils.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +[[maybe_unused]] inline uint64_t AlignUp(uint64_t value, uint64_t size) { + return value + (size - value % size) % size; +}; + +[[maybe_unused]] inline uint64_t AlignDown(uint64_t value, uint64_t size) { + return value - value % size; +}; + + diff --git a/Source/Common/StringConv.h b/Source/Common/StringConv.h new file mode 100644 index 0000000000..e060b0fd3e --- /dev/null +++ b/Source/Common/StringConv.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include +#include + +namespace FEX::StrConv { + [[maybe_unused]] static bool Conv(std::string_view Value, bool *Result) { + *Result = std::stoi(std::string(Value), nullptr, 0); + return true; + } + + [[maybe_unused]] static bool Conv(std::string_view Value, uint8_t *Result) { + *Result = std::stoi(std::string(Value), nullptr, 0); + return true; + } + + [[maybe_unused]] static bool Conv(std::string_view Value, uint16_t *Result) { + *Result = std::stoi(std::string(Value), nullptr, 0); + return true; + } + + [[maybe_unused]] static bool Conv(std::string_view Value, uint32_t *Result) { + *Result = std::stoi(std::string(Value), nullptr, 0); + return true; + } + + [[maybe_unused]] static bool Conv(std::string_view Value, int32_t *Result) { + *Result = std::stoi(std::string(Value), nullptr, 0); + return true; + } + + [[maybe_unused]] static bool Conv(std::string_view Value, uint64_t *Result) { + *Result = std::stoull(std::string(Value), nullptr, 0); + return true; + } + [[maybe_unused]] static bool Conv(std::string_view Value, std::string *Result) { + *Result = Value; + return true; + } + +} diff --git a/Source/Common/StringUtil.cpp b/Source/Common/StringUtil.cpp new file mode 100644 index 0000000000..03bd8c7c90 --- /dev/null +++ b/Source/Common/StringUtil.cpp @@ -0,0 +1,19 @@ +#include "Common/StringUtil.h" + +namespace FEX::StringUtil { +void ltrim(std::string &s) { + s.erase(std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} + +void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} +void trim(std::string &s) { + ltrim(s); + rtrim(s); +} +} diff --git a/Source/Common/StringUtil.h b/Source/Common/StringUtil.h new file mode 100644 index 0000000000..04c1788054 --- /dev/null +++ b/Source/Common/StringUtil.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +namespace FEX::StringUtil { +void ltrim(std::string &s); +void rtrim(std::string &s); +void trim(std::string &s); +} diff --git a/Source/CommonCore/CMakeLists.txt b/Source/CommonCore/CMakeLists.txt new file mode 100644 index 0000000000..0a1021b1af --- /dev/null +++ b/Source/CommonCore/CMakeLists.txt @@ -0,0 +1,6 @@ +set(NAME CommonCore) +set(SRCS + VMFactory.cpp) + +add_library(${NAME} STATIC ${SRCS}) +target_link_libraries(${NAME} FEXCore) diff --git a/Source/CommonCore/HostFactory.cpp b/Source/CommonCore/HostFactory.cpp new file mode 100644 index 0000000000..4f7e5fad96 --- /dev/null +++ b/Source/CommonCore/HostFactory.cpp @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HostFactory { + +class HostCore final : public FEXCore::CPU::CPUBackend { +public: + explicit HostCore(FEXCore::Core::ThreadState *Thread, bool Fallback); + ~HostCore() override = default; + std::string GetName() override { return "Host Stepper"; } + void* CompileCode(FEXCore::IR::IntrusiveIRList const *ir, FEXCore::Core::DebugData *DebugData) override; + + void *MapRegion(void *HostPtr, uint64_t GuestPtr, uint64_t Size) override { + // Map locally to unprotected + printf("Mapping Guest Ptr: 0x%lx\n", GuestPtr); + MemoryRegions.emplace_back(MemoryRegion{HostPtr, GuestPtr, Size}); + return HostPtr; + } + + void ExecuteCode(FEXCore::Core::ThreadState *Thread); + + void SignalHandler(int sig, siginfo_t *info, void *RawContext); + + bool NeedsOpDispatch() override { return false; } +private: + void HandleSyscall(); + void ExecutionThreadFunction(); + + void InstallSignalHandler(); + + template + T GetPointerToGuest(uint64_t Addr) { + for (auto const& Region : MemoryRegions) { + if (Addr >= Region.VirtualGuestPtr && Addr < (Region.VirtualGuestPtr + Region.Size)) { + return reinterpret_cast(reinterpret_cast(Region.HostPtr) + (Addr - Region.VirtualGuestPtr)); + } + } + + return nullptr; + } + + FEXCore::Core::ThreadState *ThreadState; + bool IsFallback; + + std::thread ExecutionThread; + struct MemoryRegion { + void *HostPtr; + uint64_t VirtualGuestPtr; + uint64_t Size; + }; + std::vector MemoryRegions; + + pid_t ChildPid; + struct sigaction OldSigAction_SEGV; + struct sigaction OldSigAction_TRAP; + + int PipeFDs[2]; + std::atomic_bool ShouldStart{false}; +}; + +static HostCore *GlobalCore; +static void SigAction_SEGV(int sig, siginfo_t* info, void* RawContext) { + GlobalCore->SignalHandler(sig, info, RawContext); +} + +HostCore::HostCore(FEXCore::Core::ThreadState *Thread, bool Fallback) + : ThreadState {Thread} + , IsFallback {Fallback} { + + GlobalCore = this; + + ExecutionThread = std::thread(&HostCore::ExecutionThreadFunction, this); +} + +static void HostExecution(FEXCore::Core::ThreadState *Thread) { + auto InternalThread = reinterpret_cast(Thread); + HostCore *Core = reinterpret_cast(InternalThread->CPUBackend.get()); + Core->ExecuteCode(Thread); +} + +static void HostExecutionFallback(FEXCore::Core::ThreadState *Thread) { + auto InternalThread = reinterpret_cast(Thread); + HostCore *Core = reinterpret_cast(InternalThread->FallbackBackend.get()); + Core->ExecuteCode(Thread); +} + +void HostCore::SignalHandler(int sig, siginfo_t *info, void *RawContext) { + ucontext_t* Context = (ucontext_t*)RawContext; + static uint64_t LastEMURip = ~0ULL; + static uint64_t LastInstSize = 0; + if (sig == SIGSEGV) { + // RIP == Base instruction + } + else if (sig == SIGTRAP) { + HostToEmuRIP -= 1; // 0xCC moves us ahead by one + } + + uint8_t *LocalData = GetPointerToGuest(Context->uc_mcontext.gregs[REG_RIP]); + uint8_t *ActualData = ThreadState->CPUCore->MemoryMapper->GetPointer(HostToEmuRIP); + uint64_t TotalInstructionsLength {0}; + + ThreadState->CPUCore->FrontendDecoder.DecodeInstructionsInBlock(ActualData, HostToEmuRIP); + auto DecodedOps = ThreadState->CPUCore->FrontendDecoder.GetDecodedInsts(); + if (sig == SIGSEGV) { + for (size_t i = 0; i < DecodedOps.second; ++i) { + FEXCore::X86Tables::DecodedInst const* DecodedInfo {nullptr}; + DecodedInfo = &DecodedOps.first->at(i); + auto CheckOp = [&](char const* Name, FEXCore::X86Tables::DecodedOperand const &Operand) { + if (Operand.TypeNone.Type != FEXCore::X86Tables::DecodedOperand::TYPE_NONE && + Operand.TypeNone.Type != FEXCore::X86Tables::DecodedOperand::TYPE_LITERAL && + Operand.TypeNone.Type != FEXCore::X86Tables::DecodedOperand::TYPE_GPR) { + printf("Operand type is %d\n", Operand.TypeNone.Type); + const std::vector EmulatorToSystemContext = { + offsetof(mcontext_t, gregs[REG_RAX]), + offsetof(mcontext_t, gregs[REG_RBX]), + offsetof(mcontext_t, gregs[REG_RCX]), + offsetof(mcontext_t, gregs[REG_RDX]), + offsetof(mcontext_t, gregs[REG_RSI]), + offsetof(mcontext_t, gregs[REG_RDI]), + offsetof(mcontext_t, gregs[REG_RBP]), + offsetof(mcontext_t, gregs[REG_RSP]), + offsetof(mcontext_t, gregs[REG_R8]), + offsetof(mcontext_t, gregs[REG_R9]), + offsetof(mcontext_t, gregs[REG_R10]), + offsetof(mcontext_t, gregs[REG_R11]), + offsetof(mcontext_t, gregs[REG_R12]), + offsetof(mcontext_t, gregs[REG_R13]), + offsetof(mcontext_t, gregs[REG_R14]), + offsetof(mcontext_t, gregs[REG_R15]), + }; + + // Modify the registers to match the memory operands necessary. + // This will propagate some addresses + if (Operand.TypeNone.Type == FEXCore::X86Tables::DecodedOperand::TYPE_GPR_DIRECT) { + uint64_t *GPR = (uint64_t*)((uintptr_t)&Context->uc_mcontext + EmulatorToSystemContext[Operand.TypeGPR.GPR]); + uint64_t HostPointer = LocalMemoryMapper.GetPointer(*GPR); + printf("Changing host pointer from 0x%lx to %lx\n", *GPR, HostPointer); + *GPR = HostPointer; + } + else { + OldSigAction_SEGV.sa_sigaction(sig, info, RawContext); + return; + } + } + }; + printf("Insts: %ld\n", DecodedOps.second); + CheckOp("Dest", DecodedInfo->Dest); + CheckOp("Src1", DecodedInfo->Src1); + CheckOp("Src2", DecodedInfo->Src2); + + // Reset RIP to the start of the instruction + Context->uc_mcontext.gregs[REG_RIP] = (uint64_t)LocalData; + return; + } + + OldSigAction_SEGV.sa_sigaction(sig, info, RawContext); + return; + } + else if (sig == SIGTRAP) { + for (size_t i = 0; i < DecodedOps.second; ++i) { + FEXCore::X86Tables::DecodedInst const* DecodedInfo {nullptr}; + DecodedInfo = &DecodedOps.first->at(i); + TotalInstructionsLength += DecodedInfo->InstSize; + } + + if (LastEMURip != ~0ULL) { + uint8_t *PreviousLocalData = LocalMemoryMapper.GetPointer(LastEMURip); + memset(PreviousLocalData, 0xCC, LastInstSize); + } + LastInstSize = TotalInstructionsLength; + printf("\tHit an instruction of length %ld 0x%lx 0x%lx 0x%lx\n", TotalInstructionsLength, (uint64_t)Context->uc_mcontext.gregs[REG_RIP], (uint64_t)LocalData, (uint64_t)ActualData); + memcpy(LocalData, ActualData, TotalInstructionsLength); + printf("\tData Source:"); + for (uint64_t i = 0; i < TotalInstructionsLength; ++i) { + printf("%02x ", ActualData[i]); + } + printf("\n"); + + // Reset RIP to the start of the instruction + Context->uc_mcontext.gregs[REG_RIP] = (uint64_t)LocalData; + } +} + +void HostCore::InstallSignalHandler() { + struct sigaction sa; + sa.sa_handler = nullptr; + sa.sa_sigaction = &SigAction_SEGV; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + // We use sigsegv to capture invalid memory accesses + // We then patch the GPR state of that instruction to point to the correct location + sigaction(SIGSEGV, &sa, &OldSigAction_SEGV); + // We use trapping to determine when we've stepped to a new instruction + sigaction(SIGTRAP, &sa, &OldSigAction_TRAP); +} + +void HostCore::ExecutionThreadFunction() { + printf("Host Core created\n"); + + if (pipe(PipeFDs) == -1) { + LogMan::Msg::A("Couldn't pipe"); + return; + } + ChildPid = fork(); + if (ChildPid == 0) { + // Child + // Set that we want to be traced + if (ptrace(PTRACE_TRACEME, 0, 0, 0)) { + LogMan::Msg::A("Couldn't start trace"); + } + raise(SIGSTOP); + InstallSignalHandler(); + + close(PipeFDs[1]); + using Ptr = void (*)(); + read(PipeFDs[0], &ThreadState->CPUState, sizeof(FEXCore::X86State::State)); + + printf("Child is running! 0x%lx\n", ThreadState->CPUState.rip); + Ptr Loc = LocalMemoryMapper.GetPointer(ThreadState->CPUState.rip); + Loc(); + printf("Oh Snap. We returned in the child\n"); + } + else { + // Parent + // Parent will be the ptrace control thread + int status; + close(PipeFDs[0]); + + waitpid(ChildPid, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) == 19) { + // Attach to child + ptrace(PTRACE_ATTACH, ChildPid, 0, 0); + ptrace(PTRACE_CONT, ChildPid, 0, 0); + } + + while (!ShouldStart.load()); + printf("Telling child to go!\n"); + write(PipeFDs[1], &ThreadState->CPUState, sizeof(FEXCore::X86State::State)); + + while (1) { + + ShouldStart.store(false); + + waitpid(ChildPid, &status, 0); + if (WIFEXITED(status)) { + return; + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == 5) { + ptrace(PTRACE_CONT, ChildPid, 0, 5); + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == 11) { + ptrace(PTRACE_DETACH, ChildPid, 0, 11); + break; + } + } + } +} + +void* HostCore::CompileCode([[maybe_unused]] FEXCore::IR::IntrusiveIRList const* ir, FEXCore::Core::DebugData *DebugData) { + printf("Attempting to compile: 0x%lx\n", ThreadState->State.rip); + if (IsFallback) + return reinterpret_cast(HostExecutionFallback); + else + return reinterpret_cast(HostExecution); +} + +void HostCore::ExecuteCode(FEXCore::Core::ThreadState *Thread) { + ShouldStart = true; + while(1); +} + +FEXCore::CPU::CPUBackend *CreateHostCore(FEXCore::Core::ThreadState *Thread) { + return new HostCore(Thread); +} + +} diff --git a/Source/CommonCore/HostFactory.h b/Source/CommonCore/HostFactory.h new file mode 100644 index 0000000000..607b62abe7 --- /dev/null +++ b/Source/CommonCore/HostFactory.h @@ -0,0 +1,14 @@ +namespace FEXCore::CPU { + class CPUBackend; +} +namespace FEXCore::Context{ + struct Context; +} +namespace FEXCore::Core { + struct ThreadState; +} + +namespace HostFactory { + FEXCore::CPU::CPUBackend *CPUCreationFactory(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread); + FEXCore::CPU::CPUBackend *CPUCreationFactoryFallback(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread); +} diff --git a/Source/CommonCore/VMFactory.cpp b/Source/CommonCore/VMFactory.cpp new file mode 100644 index 0000000000..0875240e8b --- /dev/null +++ b/Source/CommonCore/VMFactory.cpp @@ -0,0 +1,293 @@ +#include "VM.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace VMFactory { + class VMCore final : public FEXCore::CPU::CPUBackend { + public: + explicit VMCore(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread, bool Fallback); + ~VMCore() override; + std::string GetName() override { return "VM Core"; } + void* CompileCode(FEXCore::IR::IRListView const *IR, FEXCore::Core::DebugData *DebugData) override; + + void *MapRegion(void *HostPtr, uint64_t VirtualGuestPtr, uint64_t Size) override { + MemoryRegions.emplace_back(MemoryRegion{HostPtr, VirtualGuestPtr, Size}); + LogMan::Throw::A(!IsInitialized, "Tried mapping a new VM region post initialization"); + return HostPtr; + } + + void Initialize() override; + void ExecuteCode(FEXCore::Core::ThreadState *Thread); + bool NeedsOpDispatch() override { return false; } + + private: + FEXCore::Context::Context* CTX; + FEXCore::Core::ThreadState *ThreadState; + bool IsFallback; + + void CopyHostStateToGuest(); + void CopyGuestCPUToHost(); + void CopyHostMemoryToGuest(); + void CopyGuestMemoryToHost(); + + void RecalculatePML4(); + + struct MemoryRegion { + void *HostPtr; + uint64_t VirtualGuestPtr; + uint64_t Size; + }; + std::vector MemoryRegions; + + struct PhysicalToVirtual { + uint64_t PhysicalPtr; + uint64_t GuestVirtualPtr; + void *HostPtr; + uint64_t Size; + }; + std::vector PhysToVirt; + + SU::VM::VMInstance *VM; + bool IsInitialized{false}; + }; + + VMCore::~VMCore() { + delete VM; + } + + VMCore::VMCore(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread, bool Fallback) + : CTX {CTX} + , ThreadState {Thread} + , IsFallback {Fallback} { + VM = SU::VM::VMInstance::Create(); + } + + void VMCore::Initialize() { + // Scan the mapped memory regions and see how much memory backing we need + uint64_t PhysicalMemoryNeeded {}; + + PhysToVirt.reserve(MemoryRegions.size()); + + for (auto &Region : MemoryRegions) { + PhysToVirt.emplace_back(PhysicalToVirtual{PhysicalMemoryNeeded, Region.VirtualGuestPtr, Region.HostPtr, Region.Size}); + PhysicalMemoryNeeded += Region.Size; + } + LogMan::Msg::D("We need 0x%lx physical memory", PhysicalMemoryNeeded); + VM->SetPhysicalMemorySize(PhysicalMemoryNeeded); + + // Map these regions in the VM + for (auto &Region : PhysToVirt) { + if (!Region.Size) continue; + VM->AddMemoryMapping(Region.GuestVirtualPtr, Region.PhysicalPtr, Region.Size); + } + + // Initialize the VM with the memory mapping + VM->Initialize(); + + CopyHostMemoryToGuest(); + + // Set initial register states + CopyHostStateToGuest(); + + IsInitialized = true; + } + + void VMCore::CopyHostMemoryToGuest() { + void *PhysBase = VM->GetPhysicalMemoryPointer(); + for (auto &Region : PhysToVirt) { + void *GuestPtr = reinterpret_cast((reinterpret_cast(PhysBase) + Region.PhysicalPtr)); + memcpy(GuestPtr, Region.HostPtr, Region.Size); + } + } + + void VMCore::CopyGuestMemoryToHost() { + void *PhysBase = VM->GetPhysicalMemoryPointer(); + for (auto &Region : PhysToVirt) { + void *GuestPtr = reinterpret_cast((reinterpret_cast(PhysBase) + Region.PhysicalPtr)); + memcpy(Region.HostPtr, GuestPtr, Region.Size); + } + } + + void VMCore::CopyHostStateToGuest() { + auto CompactRFlags = [](auto Arg) -> uint32_t { + uint32_t Res = 2; + for (int i = 0; i < 32; ++i) { + Res |= Arg->flags[i] << i; + } + return Res; + }; + + SU::VM::VMInstance::RegisterState State; + SU::VM::VMInstance::SpecialRegisterState SpecialState; + + State.rax = ThreadState->State.gregs[FEXCore::X86State::REG_RAX]; + State.rbx = ThreadState->State.gregs[FEXCore::X86State::REG_RBX]; + State.rcx = ThreadState->State.gregs[FEXCore::X86State::REG_RCX]; + State.rdx = ThreadState->State.gregs[FEXCore::X86State::REG_RDX]; + State.rsi = ThreadState->State.gregs[FEXCore::X86State::REG_RSI]; + State.rdi = ThreadState->State.gregs[FEXCore::X86State::REG_RDI]; + State.rsp = ThreadState->State.gregs[FEXCore::X86State::REG_RSP]; + State.rbp = ThreadState->State.gregs[FEXCore::X86State::REG_RBP]; + State.r8 = ThreadState->State.gregs[FEXCore::X86State::REG_R8]; + State.r9 = ThreadState->State.gregs[FEXCore::X86State::REG_R9]; + State.r10 = ThreadState->State.gregs[FEXCore::X86State::REG_R10]; + State.r11 = ThreadState->State.gregs[FEXCore::X86State::REG_R11]; + State.r12 = ThreadState->State.gregs[FEXCore::X86State::REG_R12]; + State.r13 = ThreadState->State.gregs[FEXCore::X86State::REG_R13]; + State.r14 = ThreadState->State.gregs[FEXCore::X86State::REG_R14]; + State.r15 = ThreadState->State.gregs[FEXCore::X86State::REG_R15]; + State.rip = ThreadState->State.rip; + State.rflags = CompactRFlags(&ThreadState->State); + VM->SetRegisterState(State); + + SpecialState.fs.base = ThreadState->State.fs; + SpecialState.gs.base = ThreadState->State.gs; + + VM->SetSpecialRegisterState(SpecialState); + } + + void VMCore::CopyGuestCPUToHost() { + auto UnpackRFLAGS = [](auto ThreadState, uint64_t GuestFlags) { + for (int i = 0; i < 32; ++i) { + ThreadState->flags[i] = (GuestFlags >> i) & 1; + } + }; + + SU::VM::VMInstance::RegisterState State; + SU::VM::VMInstance::SpecialRegisterState SpecialState; + + State = VM->GetRegisterState(); + SpecialState = VM->GetSpecialRegisterState(); + + // Copy the VM's register state to our host context + ThreadState->State.gregs[FEXCore::X86State::REG_RAX] = State.rax; + ThreadState->State.gregs[FEXCore::X86State::REG_RBX] = State.rbx; + ThreadState->State.gregs[FEXCore::X86State::REG_RCX] = State.rcx; + ThreadState->State.gregs[FEXCore::X86State::REG_RDX] = State.rdx; + ThreadState->State.gregs[FEXCore::X86State::REG_RSI] = State.rsi; + ThreadState->State.gregs[FEXCore::X86State::REG_RDI] = State.rdi; + ThreadState->State.gregs[FEXCore::X86State::REG_RSP] = State.rsp; + ThreadState->State.gregs[FEXCore::X86State::REG_RBP] = State.rbp; + ThreadState->State.gregs[FEXCore::X86State::REG_R8] = State.r8; + ThreadState->State.gregs[FEXCore::X86State::REG_R9] = State.r9; + ThreadState->State.gregs[FEXCore::X86State::REG_R10] = State.r10; + ThreadState->State.gregs[FEXCore::X86State::REG_R11] = State.r11; + ThreadState->State.gregs[FEXCore::X86State::REG_R12] = State.r12; + ThreadState->State.gregs[FEXCore::X86State::REG_R13] = State.r13; + ThreadState->State.gregs[FEXCore::X86State::REG_R14] = State.r14; + ThreadState->State.gregs[FEXCore::X86State::REG_R15] = State.r15; + ThreadState->State.rip = State.rip; + UnpackRFLAGS(&ThreadState->State, State.rflags); + + ThreadState->State.fs = SpecialState.fs.base; + ThreadState->State.gs = SpecialState.gs.base; + } + + + static void VMExecution(FEXCore::Core::ThreadState *Thread) { + auto InternalThread = reinterpret_cast(Thread); + VMCore *Core = reinterpret_cast(InternalThread->CPUBackend.get()); + Core->ExecuteCode(Thread); + } + + static void VMExecutionFallback(FEXCore::Core::ThreadState *Thread) { + auto InternalThread = reinterpret_cast(Thread); + VMCore *Core = reinterpret_cast(InternalThread->FallbackBackend.get()); + Core->ExecuteCode(Thread); + } + + void* VMCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView const *IR, FEXCore::Core::DebugData *DebugData) { + if (IsFallback) + return reinterpret_cast(VMExecutionFallback); + else + return reinterpret_cast(VMExecution); + } + + void VMCore::ExecuteCode(FEXCore::Core::ThreadState *Thread) { + +#if 0 + auto DumpState = [this]() { + LogMan::Msg::D("RIP: 0x%016lx", ThreadState->State.rip); + LogMan::Msg::D("RAX RBX RCX RDX"); + LogMan::Msg::D("0x%016lx 0x%016lx 0x%016lx 0x%016lx", + ThreadState->State.gregs[FEXCore::X86State::REG_RAX], + ThreadState->State.gregs[FEXCore::X86State::REG_RBX], + ThreadState->State.gregs[FEXCore::X86State::REG_RCX], + ThreadState->State.gregs[FEXCore::X86State::REG_RDX]); + LogMan::Msg::D("RSI RDI RSP RBP"); + LogMan::Msg::D("0x%016lx 0x%016lx 0x%016lx 0x%016lx", + ThreadState->State.gregs[FEXCore::X86State::REG_RSI], + ThreadState->State.gregs[FEXCore::X86State::REG_RDI], + ThreadState->State.gregs[FEXCore::X86State::REG_RSP], + ThreadState->State.gregs[FEXCore::X86State::REG_RBP]); + LogMan::Msg::D("R8 R9 R10 R11"); + LogMan::Msg::D("0x%016lx 0x%016lx 0x%016lx 0x%016lx", + ThreadState->State.gregs[FEXCore::X86State::REG_R8], + ThreadState->State.gregs[FEXCore::X86State::REG_R9], + ThreadState->State.gregs[FEXCore::X86State::REG_R10], + ThreadState->State.gregs[FEXCore::X86State::REG_R11]); + LogMan::Msg::D("R12 R13 R14 R15"); + LogMan::Msg::D("0x%016lx 0x%016lx 0x%016lx 0x%016lx", + ThreadState->State.gregs[FEXCore::X86State::REG_R12], + ThreadState->State.gregs[FEXCore::X86State::REG_R13], + ThreadState->State.gregs[FEXCore::X86State::REG_R14], + ThreadState->State.gregs[FEXCore::X86State::REG_R15]); + }; +#endif + + CopyHostMemoryToGuest(); + CopyHostStateToGuest(); + VM->SetStepping(true); + + if (VM->Run()) { + int ExitReason = VM->ExitReason(); + + // 4 = DEBUG + if (ExitReason == 4 || ExitReason == 5) { + CopyGuestCPUToHost(); + CopyGuestMemoryToHost(); + } + + // 5 = HLT + if (ExitReason == 5) { + ThreadState->RunningEvents.ShouldStop = true; + } + + // 8 = Shutdown. Due to an unhandled error + if (ExitReason == 8) { + LogMan::Msg::E("Unhandled VM Fault"); + VM->Debug(); + CopyGuestCPUToHost(); + ThreadState->RunningEvents.ShouldStop = true; + } + + // 9 = Failed Entry + if (ExitReason == 9) { + LogMan::Msg::E("Failed to enter VM due to: 0x%lx", VM->GetFailEntryReason()); + ThreadState->RunningEvents.ShouldStop = true; + } + } + else { + LogMan::Msg::E("VM failed to run"); + ThreadState->RunningEvents.ShouldStop = true; + } + } + + FEXCore::CPU::CPUBackend *CPUCreationFactory(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread) { + return new VMCore(CTX, Thread, false); + } + + FEXCore::CPU::CPUBackend *CPUCreationFactoryFallback(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread) { + return new VMCore(CTX, Thread, true); + } + +} + diff --git a/Source/CommonCore/VMFactory.h b/Source/CommonCore/VMFactory.h new file mode 100644 index 0000000000..6caa56e8d0 --- /dev/null +++ b/Source/CommonCore/VMFactory.h @@ -0,0 +1,14 @@ +namespace FEXCore::CPU { + class CPUBackend; +} +namespace FEXCore::Context{ + struct Context; +} +namespace FEXCore::Core { + struct ThreadState; +} + +namespace VMFactory { + FEXCore::CPU::CPUBackend *CPUCreationFactory(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread); + FEXCore::CPU::CPUBackend *CPUCreationFactoryFallback(FEXCore::Context::Context* CTX, FEXCore::Core::ThreadState *Thread); +} diff --git a/Source/Core/CPU/HostCore/HostCore.cpp b/Source/Core/CPU/HostCore/HostCore.cpp new file mode 100644 index 0000000000..1d70f3da56 --- /dev/null +++ b/Source/Core/CPU/HostCore/HostCore.cpp @@ -0,0 +1,268 @@ +#include "Common/MathUtils.h" +#include "Core/CPU/CPUBackend.h" +#include "Core/CPU/CPUCore.h" +#include "Core/CPU/DebugData.h" +#include "Core/CPU/IR.h" +#include "Core/CPU/IntrusiveIRList.h" +#include "Core/CPU/HostCore/HostCore.h" +#include "LogManager.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FEX::CPU { + +class HostCore final : public CPUBackend { +public: + explicit HostCore(FEX::ThreadState *Thread); + ~HostCore() override = default; + std::string GetName() override { return "Host Stepper"; } + void* CompileCode(FEX::IR::IntrusiveIRList const *ir, FEX::CPU::DebugData *DebugData) override; + + void *MapRegion(void *HostPtr, uint64_t GuestPtr, uint64_t Size) override { + // Map locally to unprotected + printf("Mapping Guest Ptr: 0x%lx\n", GuestPtr); + void *Mem = LocalMemoryMapper.MapRegionFlags(GuestPtr, Size, PROT_READ | PROT_WRITE | PROT_EXEC); + memset(Mem, 0xCC, Size); + return HostPtr; + } + + void ExecuteCode(FEX::ThreadState *Thread); + + void SignalHandler(int sig, siginfo_t *info, void *RawContext); + + bool NeedsOpDispatch() override { return false; } +private: + void HandleSyscall(); + void ExecutionThreadFunction(); + + void InstallSignalHandler(); + + FEX::ThreadState *ThreadState; + FEX::Config::Value ConfigMultiblock{"Multiblock", false}; + std::thread ExecutionThread; + Memmap LocalMemoryMapper{}; + pid_t ChildPid; + struct sigaction OldSigAction_SEGV; + struct sigaction OldSigAction_TRAP; + + int PipeFDs[2]; + std::atomic_bool ShouldStart{false}; +}; + +static HostCore *GlobalCore; +static void SigAction_SEGV(int sig, siginfo_t* info, void* RawContext) { + GlobalCore->SignalHandler(sig, info, RawContext); +} + +HostCore::HostCore(FEX::ThreadState *Thread) + : ThreadState {Thread} { + + GlobalCore = this; + LocalMemoryMapper.AllocateSHMRegion(1ULL << 36); + + ExecutionThread = std::thread(&HostCore::ExecutionThreadFunction, this); +} +static void HostExecution(FEX::ThreadState *Thread) { + HostCore *Core = reinterpret_cast(Thread->CPUBackend.get()); + Core->ExecuteCode(Thread); +} + +void HostCore::SignalHandler(int sig, siginfo_t *info, void *RawContext) { + ucontext_t* Context = (ucontext_t*)RawContext; + uint64_t HostToEmuRIP = (uint64_t)Context->uc_mcontext.gregs[REG_RIP] - LocalMemoryMapper.GetBaseOffset(0); + printf("We hecked up and faulted! %d Emu:0x%lx Host:0x%lx\n", sig, HostToEmuRIP, (uint64_t)Context->uc_mcontext.gregs[REG_RIP]); + static uint64_t LastEMURip = ~0ULL; + static uint64_t LastInstSize = 0; + if (sig == SIGSEGV) { + // RIP == Base instruction + } + else if (sig == SIGTRAP) { + HostToEmuRIP -= 1; // 0xCC moves us ahead by one + } + + uint8_t *LocalData = LocalMemoryMapper.GetPointer(HostToEmuRIP); + uint8_t *ActualData = ThreadState->CPUCore->MemoryMapper->GetPointer(HostToEmuRIP); + uint64_t TotalInstructionsLength {0}; + + ThreadState->CPUCore->FrontendDecoder.DecodeInstructionsInBlock(ActualData, HostToEmuRIP); + auto DecodedOps = ThreadState->CPUCore->FrontendDecoder.GetDecodedInsts(); + if (sig == SIGSEGV) { + for (size_t i = 0; i < DecodedOps.second; ++i) { + FEX::X86Tables::DecodedInst const* DecodedInfo {nullptr}; + DecodedInfo = &DecodedOps.first->at(i); + auto CheckOp = [&](char const* Name, FEX::X86Tables::DecodedOperand const &Operand) { + if (Operand.TypeNone.Type != FEX::X86Tables::DecodedOperand::TYPE_NONE && + Operand.TypeNone.Type != FEX::X86Tables::DecodedOperand::TYPE_LITERAL && + Operand.TypeNone.Type != FEX::X86Tables::DecodedOperand::TYPE_GPR) { + printf("Operand type is %d\n", Operand.TypeNone.Type); + const std::vector EmulatorToSystemContext = { + offsetof(mcontext_t, gregs[REG_RAX]), + offsetof(mcontext_t, gregs[REG_RBX]), + offsetof(mcontext_t, gregs[REG_RCX]), + offsetof(mcontext_t, gregs[REG_RDX]), + offsetof(mcontext_t, gregs[REG_RSI]), + offsetof(mcontext_t, gregs[REG_RDI]), + offsetof(mcontext_t, gregs[REG_RBP]), + offsetof(mcontext_t, gregs[REG_RSP]), + offsetof(mcontext_t, gregs[REG_R8]), + offsetof(mcontext_t, gregs[REG_R9]), + offsetof(mcontext_t, gregs[REG_R10]), + offsetof(mcontext_t, gregs[REG_R11]), + offsetof(mcontext_t, gregs[REG_R12]), + offsetof(mcontext_t, gregs[REG_R13]), + offsetof(mcontext_t, gregs[REG_R14]), + offsetof(mcontext_t, gregs[REG_R15]), + }; + + // Modify the registers to match the memory operands necessary. + // This will propagate some addresses + if (Operand.TypeNone.Type == FEX::X86Tables::DecodedOperand::TYPE_GPR_DIRECT) { + uint64_t *GPR = (uint64_t*)((uintptr_t)&Context->uc_mcontext + EmulatorToSystemContext[Operand.TypeGPR.GPR]); + uint64_t HostPointer = LocalMemoryMapper.GetPointer(*GPR); + printf("Changing host pointer from 0x%lx to %lx\n", *GPR, HostPointer); + *GPR = HostPointer; + } + else { + OldSigAction_SEGV.sa_sigaction(sig, info, RawContext); + return; + } + } + }; + printf("Insts: %ld\n", DecodedOps.second); + CheckOp("Dest", DecodedInfo->Dest); + CheckOp("Src1", DecodedInfo->Src1); + CheckOp("Src2", DecodedInfo->Src2); + + // Reset RIP to the start of the instruction + Context->uc_mcontext.gregs[REG_RIP] = (uint64_t)LocalData; + return; + } + + OldSigAction_SEGV.sa_sigaction(sig, info, RawContext); + return; + } + else if (sig == SIGTRAP) { + for (size_t i = 0; i < DecodedOps.second; ++i) { + FEX::X86Tables::DecodedInst const* DecodedInfo {nullptr}; + DecodedInfo = &DecodedOps.first->at(i); + TotalInstructionsLength += DecodedInfo->InstSize; + } + + if (LastEMURip != ~0ULL) { + uint8_t *PreviousLocalData = LocalMemoryMapper.GetPointer(LastEMURip); + memset(PreviousLocalData, 0xCC, LastInstSize); + } + LastInstSize = TotalInstructionsLength; + printf("\tHit an instruction of length %ld 0x%lx 0x%lx 0x%lx\n", TotalInstructionsLength, (uint64_t)Context->uc_mcontext.gregs[REG_RIP], (uint64_t)LocalData, (uint64_t)ActualData); + memcpy(LocalData, ActualData, TotalInstructionsLength); + printf("\tData Source:"); + for (uint64_t i = 0; i < TotalInstructionsLength; ++i) { + printf("%02x ", ActualData[i]); + } + printf("\n"); + + // Reset RIP to the start of the instruction + Context->uc_mcontext.gregs[REG_RIP] = (uint64_t)LocalData; + } +} + +void HostCore::InstallSignalHandler() { + struct sigaction sa; + sa.sa_handler = nullptr; + sa.sa_sigaction = &SigAction_SEGV; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + // We use sigsegv to capture invalid memory accesses + // We then patch the GPR state of that instruction to point to the correct location + sigaction(SIGSEGV, &sa, &OldSigAction_SEGV); + // We use trapping to determine when we've stepped to a new instruction + sigaction(SIGTRAP, &sa, &OldSigAction_TRAP); +} + +void HostCore::ExecutionThreadFunction() { + printf("Host Core created\n"); + + if (pipe(PipeFDs) == -1) { + LogMan::Msg::A("Couldn't pipe"); + return; + } + ChildPid = fork(); + if (ChildPid == 0) { + // Child + // Set that we want to be traced + if (ptrace(PTRACE_TRACEME, 0, 0, 0)) { + LogMan::Msg::A("Couldn't start trace"); + } + raise(SIGSTOP); + InstallSignalHandler(); + + close(PipeFDs[1]); + using Ptr = void (*)(); + read(PipeFDs[0], &ThreadState->CPUState, sizeof(FEX::X86State::State)); + + printf("Child is running! 0x%lx\n", ThreadState->CPUState.rip); + Ptr Loc = LocalMemoryMapper.GetPointer(ThreadState->CPUState.rip); + Loc(); + printf("Oh Snap. We returned in the child\n"); + } + else { + // Parent + // Parent will be the ptrace control thread + int status; + close(PipeFDs[0]); + + waitpid(ChildPid, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) == 19) { + // Attach to child + ptrace(PTRACE_ATTACH, ChildPid, 0, 0); + ptrace(PTRACE_CONT, ChildPid, 0, 0); + } + + while (!ShouldStart.load()); + printf("Telling child to go!\n"); + write(PipeFDs[1], &ThreadState->CPUState, sizeof(FEX::X86State::State)); + + while (1) { + + ShouldStart.store(false); + + waitpid(ChildPid, &status, 0); + if (WIFEXITED(status)) { + return; + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == 5) { + ptrace(PTRACE_CONT, ChildPid, 0, 5); + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == 11) { + ptrace(PTRACE_DETACH, ChildPid, 0, 11); + break; + } + } + } +} +void* HostCore::CompileCode([[maybe_unused]] FEX::IR::IntrusiveIRList const* ir, FEX::CPU::DebugData *DebugData) { + printf("Attempting to compile: 0x%lx\n", ThreadState->CPUState.rip); + return reinterpret_cast(HostExecution); +} + +void HostCore::ExecuteCode(FEX::ThreadState *Thread) { + ShouldStart = true; + while(1); +} + +FEX::CPU::CPUBackend *CreateHostCore(FEX::ThreadState *Thread) { + return new HostCore(Thread); +} + +} diff --git a/Source/Core/CPU/HostCore/HostCore.h b/Source/Core/CPU/HostCore/HostCore.h new file mode 100644 index 0000000000..49c4219785 --- /dev/null +++ b/Source/Core/CPU/HostCore/HostCore.h @@ -0,0 +1,10 @@ +#pragma once + +namespace FEX { +struct ThreadState; +} + +namespace FEX::CPU { +class CPUBackend; +FEX::CPU::CPUBackend *CreateHostCore(FEX::ThreadState *Thread); +} diff --git a/Source/Tests/CMakeLists.txt b/Source/Tests/CMakeLists.txt new file mode 100644 index 0000000000..605450ba76 --- /dev/null +++ b/Source/Tests/CMakeLists.txt @@ -0,0 +1,55 @@ +set(LIBS FEXCore Common CommonCore SonicUtils pthread LLVM) +set(NAME ELFLoader) +set(SRCS ELFLoader.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + +set(NAME TestHarness) +set(SRCS TestHarness.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + +set(NAME TestHarnessRunner) +set(SRCS TestHarnessRunner.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + +set(NAME LockstepRunner) +set(SRCS LockstepRunner.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + +set(NAME UnitTestGenerator) +set(SRCS UnitTestGenerator.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + +set(NAME PTrace) +set(SRCS TestSingleStepHardware.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} ${LIBS}) + diff --git a/Source/Tests/ELFLoader.cpp b/Source/Tests/ELFLoader.cpp new file mode 100644 index 0000000000..6d88e3abb1 --- /dev/null +++ b/Source/Tests/ELFLoader.cpp @@ -0,0 +1,102 @@ +#include "Common/ArgumentLoader.h" +#include "CommonCore/VMFactory.h" +#include "Common/Config.h" +#include "ELFLoader.h" +#include "HarnessHelpers.h" +#include "LogManager.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + case LogMan::STDOUT: + CharLevel = "STDOUT"; + break; + case LogMan::STDERR: + CharLevel = "STDERR"; + break; + default: + CharLevel = "???"; + break; + } + + printf("[%s] %s\n", CharLevel, Message); + fflush(nullptr); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); + fflush(nullptr); +} + +int main(int argc, char **argv, char **const envp) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + FEX::Config::Value CoreConfig{"Core", 0}; + FEX::Config::Value BlockSizeConfig{"MaxInst", 1}; + FEX::Config::Value SingleStepConfig{"SingleStep", false}; + FEX::Config::Value MultiblockConfig{"Multiblock", false}; + + auto Args = FEX::ArgLoader::Get(); + + LogMan::Throw::A(!Args.empty(), "Not enough arguments"); + + FEX::HarnessHelper::ELFCodeLoader Loader{Args[0], Args, envp}; + + FEXCore::Context::InitializeStaticTables(); + auto SHM = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + auto CTX = FEXCore::Context::CreateNewContext(); + FEXCore::Context::InitializeContext(CTX); + FEXCore::Context::SetApplicationFile(CTX, std::filesystem::canonical(Args[0])); + + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_DEFAULTCORE, CoreConfig() > 3 ? FEXCore::Config::CONFIG_CUSTOM : CoreConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MULTIBLOCK, MultiblockConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, SingleStepConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MAXBLOCKINST, BlockSizeConfig()); + FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory); + // FEXCore::Context::SetFallbackCPUBackendFactory(CTX, VMFactory::CPUCreationFactoryFallback); + + FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); + FEXCore::Context::InitCore(CTX, &Loader); + + while (FEXCore::Context::RunLoop(CTX, true) == FEXCore::Context::ExitReason::EXIT_DEBUG); + auto ShutdownReason = FEXCore::Context::GetExitReason(CTX); + LogMan::Msg::D("Reason we left VM: %d", ShutdownReason); + bool Result = ShutdownReason == 0; + + FEXCore::Context::DestroyContext(CTX); + FEXCore::SHM::DestroyRegion(SHM); + + printf("Managed to load? %s\n", Result ? "Yes" : "No"); + + FEX::Config::Shutdown(); + return 0; +} diff --git a/Source/Tests/HarnessHelpers.h b/Source/Tests/HarnessHelpers.h new file mode 100644 index 0000000000..1bfc070030 --- /dev/null +++ b/Source/Tests/HarnessHelpers.h @@ -0,0 +1,598 @@ +#pragma once +#include "Common/Config.h" +#include "Common/MathUtils.h" +#include "ELFLoader.h" +#include "ELFSymbolDatabase.h" +#include "LogManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FEX::HarnessHelper { + bool CompareStates(FEXCore::Core::CPUState const& State1, + FEXCore::Core::CPUState const& State2, + uint64_t MatchMask, + bool OutputGPRs) { + bool Matches = true; + + auto DumpGPRs = [OutputGPRs](auto Name, uint64_t A, uint64_t B) { + if (!OutputGPRs) return; + if (A == B) return; + + printf("%s: 0x%016lx %s 0x%016lx\n", Name.c_str(), A, A==B ? "==" : "!=", B); + }; + + auto DumpFLAGs = [OutputGPRs](auto Name, uint64_t A, uint64_t B) { + if (!OutputGPRs) return; + if (A == B) return; + + constexpr std::array Flags = { + FEXCore::X86State::RFLAG_CF_LOC, + FEXCore::X86State::RFLAG_PF_LOC, + FEXCore::X86State::RFLAG_AF_LOC, + FEXCore::X86State::RFLAG_ZF_LOC, + FEXCore::X86State::RFLAG_SF_LOC, + FEXCore::X86State::RFLAG_TF_LOC, + FEXCore::X86State::RFLAG_IF_LOC, + FEXCore::X86State::RFLAG_DF_LOC, + FEXCore::X86State::RFLAG_OF_LOC, + FEXCore::X86State::RFLAG_IOPL_LOC, + FEXCore::X86State::RFLAG_NT_LOC, + FEXCore::X86State::RFLAG_RF_LOC, + FEXCore::X86State::RFLAG_VM_LOC, + FEXCore::X86State::RFLAG_AC_LOC, + FEXCore::X86State::RFLAG_VIF_LOC, + FEXCore::X86State::RFLAG_VIP_LOC, + FEXCore::X86State::RFLAG_ID_LOC, + }; + + printf("%s: 0x%016lx %s 0x%016lx\n", Name.c_str(), A, A==B ? "==" : "!=", B); + for (auto &Flag : Flags) { + uint64_t FlagMask = 1 << Flag; + if ((A & FlagMask) != (B & FlagMask)) { + printf("\t%s: %ld != %ld\n", FEXCore::Core::GetFlagName(Flag).data(), (A >> Flag) & 1, (B >> Flag) & 1); + } + } + }; + + auto CheckGPRs = [&Matches, DumpGPRs](std::string Name, uint64_t A, uint64_t B){ + DumpGPRs(std::move(Name), A, B); + Matches &= A == B; + }; + + auto CheckFLAGS = [&Matches, DumpFLAGs](std::string Name, uint64_t A, uint64_t B){ + DumpFLAGs(std::move(Name), A, B); + Matches &= A == B; + }; + + + // RIP + if (MatchMask & 1) { + CheckGPRs("RIP", State1.rip, State2.rip); + } + + MatchMask >>= 1; + + // GPRS + for (unsigned i = 0; i < 16; ++i, MatchMask >>= 1) { + if (MatchMask & 1) { + CheckGPRs("GPR" + std::to_string(i), State1.gregs[i], State2.gregs[i]); + } + } + + // XMM + for (unsigned i = 0; i < 16; ++i, MatchMask >>= 1) { + if (MatchMask & 1) { + CheckGPRs("XMM0_" + std::to_string(i), State1.xmm[i][0], State2.xmm[i][0]); + CheckGPRs("XMM1_" + std::to_string(i), State1.xmm[i][1], State2.xmm[i][1]); + } + } + + // GS + if (MatchMask & 1) { + CheckGPRs("GS", State1.gs, State2.gs); + } + MatchMask >>= 1; + + // FS + if (MatchMask & 1) { + CheckGPRs("FS", State1.fs, State2.fs); + } + MatchMask >>= 1; + + auto CompactRFlags = [](auto Arg) -> uint32_t { + uint32_t Res = 2; + for (int i = 0; i < 32; ++i) { + Res |= Arg->flags[i] << i; + } + return Res; + }; + + // FLAGS + if (MatchMask & 1) { + uint32_t rflags1 = CompactRFlags(&State1); + uint32_t rflags2 = CompactRFlags(&State2); + + CheckFLAGS("FLAGS", rflags1, rflags2); + } + MatchMask >>= 1; + return Matches; + } + + void ReadFile(std::string const &Filename, std::vector *Data) { + std::fstream TestFile; + TestFile.open(Filename, std::fstream::in | std::fstream::binary); + LogMan::Throw::A(TestFile.is_open(), "Failed to open file"); + + TestFile.seekg(0, std::fstream::end); + size_t FileSize = TestFile.tellg(); + TestFile.seekg(0, std::fstream::beg); + + Data->resize(FileSize); + + TestFile.read(&Data->at(0), FileSize); + + TestFile.close(); + } + + class ConfigLoader final { + public: + void Init(std::string const &ConfigFilename) { + ReadFile(ConfigFilename, &RawConfigFile); + memcpy(&BaseConfig, RawConfigFile.data(), sizeof(ConfigStructBase)); + } + + bool CompareStates(FEXCore::Core::CPUState const& State1, FEXCore::Core::CPUState const& State2) { + bool Matches = true; + uint64_t MatchMask = BaseConfig.OptionMatch & ~BaseConfig.OptionIgnore; + Matches &= FEX::HarnessHelper::CompareStates(State1, State2, MatchMask, ConfigDumpGPRs()); + + if (BaseConfig.OptionRegDataCount > 0) { + uintptr_t DataOffset = sizeof(ConfigStructBase); + constexpr std::array, 36> OffsetArray = {{ + {offsetof(FEXCore::Core::CPUState, rip), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[0]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[1]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[2]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[3]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[4]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[5]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[6]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[7]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[8]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[9]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[10]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[11]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[12]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[13]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[14]), 1}, + {offsetof(FEXCore::Core::CPUState, gregs[15]), 1}, + {offsetof(FEXCore::Core::CPUState, xmm[0][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[1][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[2][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[3][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[4][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[5][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[6][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[7][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[8][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[9][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[10][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[11][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[12][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[13][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[14][0]), 2}, + {offsetof(FEXCore::Core::CPUState, xmm[15][0]), 2}, + {offsetof(FEXCore::Core::CPUState, gs), 1}, + {offsetof(FEXCore::Core::CPUState, fs), 1}, + {offsetof(FEXCore::Core::CPUState, flags), 8}, + }}; + + // Offset past the Memory regions if there are any + DataOffset += sizeof(MemoryRegionBase) * BaseConfig.OptionMemoryRegionCount; + for (unsigned i = 0; i < BaseConfig.OptionRegDataCount; ++i) { + RegDataStructBase *RegData = reinterpret_cast(RawConfigFile.data() + DataOffset); + [[maybe_unused]] std::bitset<64> RegFlags = RegData->RegKey; + assert(RegFlags.count() == 1 && "Must set reg data explicitly per register"); + assert(RegData->RegKey != (1UL << 36) && "Invalid register selection"); + + size_t NameIndex = __builtin_ffsl(RegData->RegKey)- 1; + auto Offset = OffsetArray[NameIndex]; + uint64_t *State1Data = reinterpret_cast(reinterpret_cast(&State1) + Offset.first); + uint64_t *State2Data = reinterpret_cast(reinterpret_cast(&State2) + Offset.first); + + auto DumpGPRs = [this](auto Name, uint64_t A, uint64_t B) { + if (!ConfigDumpGPRs()) + return; + + printf("%s: 0x%016lx %s 0x%016lx\n", Name.c_str(), A, A==B ? "==" : "!=", B); + }; + + auto CheckGPRs = [&Matches, DumpGPRs](std::string Name, uint64_t A, uint64_t B) { + DumpGPRs(std::move(Name), A, B); + Matches &= A == B; + }; + + for (unsigned j = 0; j < Offset.second; ++j) { + std::string Name; + if (NameIndex == 0) // RIP + Name = "RIP"; + else if (NameIndex >= 1 && NameIndex < 17) + Name = "GPR" + std::to_string(NameIndex - 1); + else if (NameIndex >= 17 && NameIndex < 33) + Name = "XMM[" + std::to_string(NameIndex - 17) + "][" + std::to_string(j) + "]"; + else if (NameIndex == 33) + Name = "gs"; + else if (NameIndex == 34) + Name ="fs"; + else if (NameIndex == 35) + Name = "rflags"; + + CheckGPRs("Core1: " + Name + ": ", State1Data[j], RegData->RegValues[j]); + CheckGPRs("Core2: " + Name + ": ", State2Data[j], RegData->RegValues[j]); + } + + // Get the correct data offset + DataOffset += sizeof(RegDataStructBase) + Offset.second * 8; + } + } + return Matches; + } + + private: + FEX::Config::Value ConfigDumpGPRs{"DumpGPRs", false}; + + struct ConfigStructBase { + uint64_t OptionMatch; + uint64_t OptionIgnore; + uint64_t OptionStackSize; + uint64_t OptionEntryPoint; + uint32_t OptionABI; + uint32_t OptionMemoryRegionCount; + uint32_t OptionRegDataCount; + uint8_t AdditionalData[]; + }__attribute__((packed)); + + struct MemoryRegionBase { + uint64_t Region; + uint64_t Size; + } __attribute__((packed)); + + struct RegDataStructBase { + uint32_t RegDataCount; + uint64_t RegKey; + uint64_t RegValues[]; + } __attribute__((packed)); + + std::vector RawConfigFile; + ConfigStructBase BaseConfig; + }; + + class HarnessCodeLoader final : public FEXCore::CodeLoader { + + static constexpr uint32_t PAGE_SIZE = 4096; + public: + + HarnessCodeLoader(std::string const &Filename, const char *ConfigFilename) { + ReadFile(Filename, &RawFile); + if (ConfigFilename) { + Config.Init(ConfigFilename); + } + } + + uint64_t StackSize() const override { + return STACK_SIZE; + } + + uint64_t SetupStack([[maybe_unused]] void *HostPtr, uint64_t GuestPtr) const override { + return GuestPtr + STACK_SIZE - 16; + } + + uint64_t DefaultRIP() const override { + return RIP; + } + + MemoryLayout GetLayout() const override { + uint64_t CodeSize = RawFile.size(); + CodeSize = AlignUp(CodeSize, PAGE_SIZE); + return std::make_tuple(CODE_START_RANGE, CODE_START_RANGE + CodeSize, CodeSize); + } + + void MapMemoryRegion(std::function Mapper) override { + bool LimitedSize = true; + if (LimitedSize) { + Mapper(0xe000'0000, PAGE_SIZE * 10); + + // SIB8 + // We test [-128, -126] (Bottom) + // We test [-8, 8] (Middle) + // We test [120, 127] (Top) + // Can fit in two pages + Mapper(0xe800'0000 - PAGE_SIZE, PAGE_SIZE * 2); + + // SIB32 Bottom + // We test INT_MIN, INT_MIN + 8 + Mapper(0x2'0000'0000, PAGE_SIZE); + // SIB32 Middle + // We test -8 + 8 + Mapper(0x2'8000'0000 - PAGE_SIZE, PAGE_SIZE * 2); + + // SIB32 Top + // We Test INT_MAX - 8, INT_MAX + Mapper(0x3'0000'0000 - PAGE_SIZE, PAGE_SIZE * 2); + } + else { + // This is scratch memory location and SIB8 location + Mapper(0xe000'0000, 0x1000'0000); + // This is for large SIB 32bit displacement testing + Mapper(0x2'0000'0000, 0x1'0000'1000); + } + + // Map in the memory region for the test file + Mapper(CODE_START_PAGE, AlignUp(RawFile.size(), PAGE_SIZE)); + } + + void LoadMemory(MemoryWriter Writer) override { + // Memory base here starts at the start location we passed back with GetLayout() + // This will write at [CODE_START_RANGE + 0, RawFile.size() ) + Writer(&RawFile.at(0), CODE_START_RANGE, RawFile.size()); + } + + uint64_t GetFinalRIP() override { return CODE_START_RANGE + RawFile.size(); } + + bool CompareStates(FEXCore::Core::CPUState const& State1, FEXCore::Core::CPUState const& State2) { + return Config.CompareStates(State1, State2); + } + + private: + constexpr static uint64_t STACK_SIZE = PAGE_SIZE; + // Zero is special case to know when we are done + constexpr static uint64_t CODE_START_PAGE = 0x0'1000; + constexpr static uint64_t CODE_START_RANGE = CODE_START_PAGE + 0x1; + constexpr static uint64_t RIP = CODE_START_RANGE; + + std::vector RawFile; + ConfigLoader Config; + }; + +class ELFCodeLoader final : public FEXCore::CodeLoader { +public: + ELFCodeLoader(std::string const &Filename, [[maybe_unused]] std::vector const &args, char **const envp = nullptr) + : File {Filename, false} + , DB {&File} + , Args {args} { + + if (File.WasDynamic()) { + // If the file isn't static then we need to add the filename of interpreter + // to the front of the argument list + Args.emplace(Args.begin(), File.InterpreterLocation()); + } + + // Just block pretty much everything + const std::vector NotAllowed = { + "XDG_CONFIG_DIRS", + "XDG_SEAT", + "XDG_SESSION_DESKTOP", + "XDG_SESSION_TYPE", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_CLASS", + "XDG_VTNR", + "XDG_SESSION_ID", + "XDG_RUNTIME_DIR", + "XDG_DATA_DIRS", + + "QT_ACCESSIBILITY", + "COLORTERM", + "DERBY_HOME", + "TERMCAP", + "JAVA_HOME", + "SSH_AUTH_SOCK", + "DESKTOP_SESSION", + "SSH_AGENT_PID", + "GTK_MODULES", + "LOGNAME", + "GPG_AGENT_INFO", + "XAUTHORITY", + "J2REDIR", + "WINDOWPATH", + "HOME", + "LANG", + "LS_COLORS", + "PM_PACKAGES_ROOT", + "TERM", + "DEFAULT_PATH", + "J2SDKDIR", + "LESSCLOSE", + "LESSOPEN", + "LIBVIRT_DEFAULT_URI", + "USER", + "DISPLAY", + "SHLVL", + "PATH", + "STY", + "GDMSESSION", + "DBUS_SESSION_BUS_ADDRESS", + "OLDPWD", + "_", + + "WINIT_HIDPI_FACTOR", + "SHELL", + "WINDOWID", + "MANDATORY_PATH", + "WINDOW", + "PWD", + "DEFAULTS_PATH", + + "ALACRITTY_LOG", + "USERNAME", + }; + + if (!!envp) { + // If we had envp passed in then make sure to set it up on the guest + for (unsigned i = 0;; ++i) { + if (envp[i] == nullptr) + break; + bool Invalid = false; + + for (auto Str : NotAllowed) { + if (strncmp(Str, envp[i], strlen(Str)) == 0) { + Invalid = true; + break; + } + } + + if (Invalid) continue; + + printf("Allowing '%s'\n", envp[i]); + EnvironmentVariables.emplace_back(envp[i]); + } + } + } + + uint64_t StackSize() const override { + return STACK_SIZE; + } + + uint64_t SetupStack(void *HostPtr, uint64_t GuestPtr) const override { + uintptr_t StackPointer = reinterpret_cast(HostPtr) + StackSize(); + // Set up our initial CPU state + uint64_t rsp = GuestPtr + StackSize(); + + uint64_t TotalArgumentMemSize{}; + uint64_t ArgumentBackingSize{}; + + TotalArgumentMemSize += 8; // Argument counter size + TotalArgumentMemSize += 8 * Args.size(); // Pointers to strings + TotalArgumentMemSize += 8; // Padding for something + TotalArgumentMemSize += 8 * EnvironmentVariables.size(); // Argument location for envp + + for (unsigned i = 0; i < Args.size(); ++i) { + TotalArgumentMemSize += Args[i].size() + 1; + ArgumentBackingSize += Args[i].size() + 1; + } + + for (unsigned i = 0; i < EnvironmentVariables.size(); ++i) { + TotalArgumentMemSize += EnvironmentVariables[i].size() + 1; + } + + TotalArgumentMemSize += 8; + + // Burn some SP space for a redzone + rsp -= TotalArgumentMemSize + 0x1000; + StackPointer -= TotalArgumentMemSize + 0x1000; + + // Stack setup + // [0, 8): Argument Count + // [8, 16): Argument Pointer 0 + // [16, 24): Argument Pointer 1 + // .... + // [Pad1, +8): Some Pointer + // [envp, +8): envp pointer + // [Pad2End, +8): Argument String 0 + // [+8, +8): String 1 + // ... + // [argvend, +8): envp[0] + // ... + // [envpend, +8): nullptr + + uint64_t *ArgumentPointers = reinterpret_cast(StackPointer + 8); + uint64_t *PadPointers = reinterpret_cast(StackPointer + 8 + Args.size() * 8); + uint64_t *EnvpPointers = reinterpret_cast(StackPointer + 8 + Args.size() * 8 + 8); + + uint64_t ArgumentBackingOffset = 8 * Args.size() + 8 * 4; + uint8_t *ArgumentBackingBase = reinterpret_cast(StackPointer + ArgumentBackingOffset); + uint64_t ArgumentBackingBaseGuest = rsp + ArgumentBackingOffset; + + uint64_t EnvpBackingOffset = ArgumentBackingOffset + ArgumentBackingSize + EnvironmentVariables.size() * 8; + uint8_t *EnvpBackingBase = reinterpret_cast(StackPointer + EnvpBackingOffset); + uint64_t EnvpBackingBaseGuest = rsp + EnvpBackingOffset; + + *reinterpret_cast(StackPointer + 0) = Args.size(); + PadPointers[0] = 0; + + // If we don't have any, just make sure the first is nullptr + EnvpPointers[0] = 0; + + uint64_t CurrentOffset = 0; + for (size_t i = 0; i < Args.size(); ++i) { + size_t ArgSize = Args[i].size(); + // Set the pointer to this argument + ArgumentPointers[i] = ArgumentBackingBaseGuest + CurrentOffset; + // Copy the string in to the final location + memcpy(reinterpret_cast(ArgumentBackingBase + CurrentOffset), &Args[i].at(0), ArgSize); + + // Set the null terminator for the string + *reinterpret_cast(ArgumentBackingBase + CurrentOffset + ArgSize + 1) = 0; + + CurrentOffset += ArgSize + 1; + } + + CurrentOffset = 0; + for (size_t i = 0; i < EnvironmentVariables.size(); ++i) { + size_t EnvpSize = EnvironmentVariables[i].size(); + // Set the pointer to this argument + EnvpPointers[i] = EnvpBackingBaseGuest + CurrentOffset; + + // Copy the string in to the final location + memcpy(reinterpret_cast(EnvpBackingBase + CurrentOffset), &EnvironmentVariables[i].at(0), EnvpSize); + + // Set the null terminator for the string + *reinterpret_cast(EnvpBackingBase + CurrentOffset + EnvpSize + 1) = 0; + + CurrentOffset += EnvpSize + 1; + } + // Last envp needs to be nullptr + EnvpPointers[EnvironmentVariables.size()] = 0; + + return rsp; + } + + uint64_t DefaultRIP() const override { + return DB.DefaultRIP(); + } + + void MapMemoryRegion(std::function Mapper) override { + DB.MapMemoryRegions(Mapper); + } + + MemoryLayout GetLayout() const override { + return DB.GetFileLayout(); + } + + void LoadMemory(MemoryWriter Writer) override { + DB.WriteLoadableSections(Writer); + } + + char const *FindSymbolNameInRange(uint64_t Address) override { + ELFLoader::ELFSymbol const *Sym; + Sym = DB.GetSymbolInRange(std::make_pair(Address, 1)); + if (Sym) { + return Sym->Name; + } + return nullptr; + } + + void GetInitLocations(std::vector *Locations) override { + DB.GetInitLocations(Locations); + } + + uint64_t InitializeThreadSlot(std::function Writer) const override { + return DB.InitializeThreadSlot(Writer); + }; + +private: + ::ELFLoader::ELFContainer File; + ::ELFLoader::ELFSymbolDatabase DB; + std::vector Args; + std::vector EnvironmentVariables; + constexpr static uint64_t STACK_SIZE = 8 * 1024 * 1024; +}; + +} diff --git a/Source/Tests/LockstepRunner.cpp b/Source/Tests/LockstepRunner.cpp new file mode 100644 index 0000000000..dd5dcc8faf --- /dev/null +++ b/Source/Tests/LockstepRunner.cpp @@ -0,0 +1,738 @@ +#include "Common/ArgumentLoader.h" +#include "Common/Config.h" +#include "CommonCore/VMFactory.h" +#include "HarnessHelpers.h" +#include "LogManager.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + default: + CharLevel = "???"; + break; + } + printf("[%s] %s\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); +} + +class Flag final { +public: + bool TestAndSet(bool SetValue = true) { + bool Expected = !SetValue; + return Value.compare_exchange_strong(Expected, SetValue); + } + + bool TestAndClear() { + return TestAndSet(false); + } + + void Set() { + Value.store(true); + } + + bool Load() { + return Value.load(); + } + +private: + std::atomic_bool Value {false}; +}; + + +class IPCEvent final { +private: +/** + * @brief Literally just an atomic bool that we are using for this class + */ +public: + IPCEvent(std::string_view Name, bool Client) + : EventName {Name} { + + using namespace boost::interprocess; + if (!Client) { + shared_memory_object::remove(EventName.c_str()); + SHM = std::make_unique( + create_only, + EventName.c_str(), + read_write + ); + SHMSize = sizeof(SHMObject); + SHM->truncate(SHMSize); + SHMRegion = std::make_unique(*SHM, read_write); + + Obj = new (SHMRegion->get_address()) SHMObject{}; + } + else { + SHM = std::make_unique( + open_only, + EventName.c_str(), + read_write + ); + SHMSize = sizeof(SHMObject); + SHMRegion = std::make_unique(*SHM, read_write); + + // Load object from shared memory, don't construct it + Obj = reinterpret_cast(SHMRegion->get_address()); + } + } + + ~IPCEvent() { + using namespace boost::interprocess; + shared_memory_object::remove(EventName.c_str()); + } + + void NotifyOne() { + using namespace boost::interprocess; + if (Obj->FlagObject.TestAndSet()) { + scoped_lock lk(Obj->MutexObject); + Obj->CondObject.notify_one(); + } + } + + void NotifyAll() { + using namespace boost::interprocess; + if (Obj->FlagObject.TestAndSet()) { + scoped_lock lk(Obj->MutexObject); + Obj->CondObject.notify_all(); + } + } + + void Wait() { + using namespace boost::interprocess; + + // Have we signaled before we started waiting? + if (Obj->FlagObject.TestAndClear()) + return; + + scoped_lock lk(Obj->MutexObject); + Obj->CondObject.wait(lk, [this] { return Obj->FlagObject.TestAndClear(); }); + } + + bool WaitFor(boost::posix_time::ptime const& time) { + using namespace boost::interprocess; + + // Have we signaled before we started waiting? + if (Obj->FlagObject.TestAndClear()) + return true; + + scoped_lock lk(Obj->MutexObject); + bool DidSignal = Obj->CondObject.timed_wait(lk, time, [this] { return Obj->FlagObject.TestAndClear(); }); + return DidSignal; + } + +private: + std::string EventName; + std::unique_ptr SHM; + std::unique_ptr SHMRegion; + struct SHMObject { + Flag FlagObject; + boost::interprocess::interprocess_condition CondObject; + boost::interprocess::interprocess_mutex MutexObject; + }; + SHMObject *Obj; + size_t SHMSize; +}; + +class IPCFlag { +public: + + IPCFlag(std::string_view Name, bool Client) + : EventName {Name} { + + using namespace boost::interprocess; + if (!Client) { + shared_memory_object::remove(EventName.c_str()); + SHM = std::make_unique( + create_only, + EventName.c_str(), + read_write + ); + SHMSize = sizeof(FlagObj); + SHM->truncate(SHMSize); + SHMRegion = std::make_unique(*SHM, read_write); + + FlagObj = new (SHMRegion->get_address()) Flag{}; + } + else { + SHM = std::make_unique( + open_only, + EventName.c_str(), + read_write + ); + SHMSize = sizeof(Flag); + SHMRegion = std::make_unique(*SHM, read_write); + + // Load object from shared memory, don't construct it + FlagObj = reinterpret_cast(SHMRegion->get_address()); + } + } + + ~IPCFlag() { + using namespace boost::interprocess; + shared_memory_object::remove(EventName.c_str()); + } + + Flag *GetFlag() { return FlagObj; } + +private: + std::string EventName; + std::unique_ptr SHM; + std::unique_ptr SHMRegion; + size_t SHMSize; + + Flag *FlagObj; +}; + +class IPCMessage { +public: + IPCMessage(std::string_view Name, bool Client, size_t QueueSize, size_t QueueDepth) + : QueueName {Name} { + using namespace boost::interprocess; + if (!Client) { + message_queue::remove(QueueName.c_str()); + MessageQueue = std::make_unique( + create_only, + QueueName.c_str(), + QueueDepth, + QueueSize + ); + } + else { + MessageQueue = std::make_unique( + open_only, + QueueName.c_str() + ); + } + } + + void Send(void *Buf, size_t Size) { + MessageQueue->send(Buf, Size, 0); + } + + size_t Recv(void *Buf, size_t Size) { + uint32_t Priority {}; + size_t Recv_Size{}; + if (!MessageQueue->timed_receive(Buf, Size, Recv_Size, Priority, boost::get_system_time() + boost::posix_time::seconds(10))) { + return 0; + } + return Recv_Size; + } + + void ThrowAwayRecv() { + uint32_t Priority {}; + size_t Recv_Size{}; + MessageQueue->try_receive(nullptr, 0, Recv_Size, Priority); + } + + ~IPCMessage() { + using namespace boost::interprocess; + message_queue::remove(QueueName.c_str()); + } + +private: + std::string QueueName; + std::unique_ptr MessageQueue; +}; + +/** + * @brief Heartbeat to know if processes are alive + */ +class IPCHeartbeat { +public: + IPCHeartbeat(std::string_view Name, bool Client) + : EventName {Name} { + using namespace boost::interprocess; + + if (Client) { + bool Trying = true; + while (Trying) { + try { + SHM = std::make_unique( + open_only, + EventName.c_str(), + read_write + ); + + boost::interprocess::offset_t size; + while (!SHM->get_size(size)) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + + // Map the SHM region + // This can fault for...some reason? + SHMRegion = std::make_unique(*SHM, read_write); + + HeartBeatEvents = reinterpret_cast(SHMRegion->get_address()); + while (HeartBeatEvents->Allocated.load() == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + Trying = false; + } + catch(...) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + } + else { + SHM = std::make_unique( + open_or_create, + EventName.c_str(), + read_write + ); + + SHM->truncate(sizeof(SHMHeartbeatEvent) + sizeof(HeartbeatClient) * 32); + + // Map the SHM region + SHMRegion = std::make_unique(*SHM, read_write); + + HeartBeatEvents = new (SHMRegion->get_address()) SHMHeartbeatEvent{}; + HeartBeatEvents->Allocated = 1; + } + + ThreadID = AllocateThreadID(); + if (ThreadID == ~0U) { + Running = false; + return; + } + HeartBeatThread = std::thread(&IPCHeartbeat::Thread, this); + } + + ~IPCHeartbeat() { + using namespace boost::interprocess; + Running = false; + if (HeartBeatThread.joinable()) { + HeartBeatThread.join(); + } + + // If we are the last client then we should just destroy this region + if (DeallocateThreadID(ThreadID)) { + shared_memory_object::remove(EventName.c_str()); + } + } + + bool WaitForServer() { + auto Start = std::chrono::system_clock::now(); + while (!IsServerAlive()) { + auto Now = std::chrono::system_clock::now(); + using namespace std::chrono_literals; + if ((Now - Start) >= 10s) { + // We waited 10 seconds and the server never came up + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return true; + } + + bool WaitForClient() { + auto Start = std::chrono::system_clock::now(); + while (!IsClientAlive()) { + auto Now = std::chrono::system_clock::now(); + using namespace std::chrono_literals; + if ((Now - Start) >= 10s) { + // We waited 10 seconds and the server never came up + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return true; + } + + + void Thread() { + HeartbeatClient *ThisThread = &HeartBeatEvents->Clients[ThreadID]; + while (Running) { + std::this_thread::sleep_for(HeartBeatRate); + ThisThread->HeartbeatCounter++; + } + } + + /** + * @brief Once the heartbeat is active we can enable a server flag + */ + void EnableServer() { + LogMan::Msg::E("[SERVER %s] Enabling server on thread ID %d", EventName.c_str(), ThreadID); + HeartbeatClient *ThisThread = &HeartBeatEvents->Clients[ThreadID]; + ThisThread->Type = 1; + } + + void EnableClient() { + LogMan::Msg::E("[CLIENT %s] Enabling client on thread ID %d", EventName.c_str(), ThreadID); + HeartbeatClient *ThisThread = &HeartBeatEvents->Clients[ThreadID]; + ThisThread->Type = 2; + } + +private: + + bool IsServerAlive() { + return IsTypeAlive(1); + } + + bool IsClientAlive() { + return IsTypeAlive(2); + } + + bool IsTypeAlive(uint32_t Type) { + uint32_t OriginalThreadMask = HeartBeatEvents->NumClients.load(); + for (uint32_t Index = 0; Index < 32; ++Index) { + if ((OriginalThreadMask & (1 << Index)) == 0) + continue; + + HeartbeatClient *ThisThread = &HeartBeatEvents->Clients[Index]; + if (ThisThread->Type == Type) { + // Found a Server, check if it is live + + uint32_t Counter = ThisThread->HeartbeatCounter.load(); + std::this_thread::sleep_for(HeartBeatRate * 2); + if (Counter != ThisThread->HeartbeatCounter.load()) { + return true; + } + } + } + + return false; + } + + + uint32_t AllocateThreadID() { + while (true) { + uint32_t Index = 0; + uint32_t OriginalIDMask = HeartBeatEvents->NumClients.load(); + if (OriginalIDMask == ~0U) { + // All slots taken + return ~0U; + } + + // Scan every index and try to find a free slot + while (Index != 33) { + uint32_t Slot = 1 << Index; + if ((OriginalIDMask & Slot) == 0) { + // Free slot, let's try and take it + uint32_t NewIDMask = OriginalIDMask | Slot; + + if (HeartBeatEvents->NumClients.compare_exchange_strong(OriginalIDMask, NewIDMask)) { + // We successfully got a slow + return Index; + } + else { + // We failed to get a slot, try again + break; + } + } + + // Try next index + ++Index; + } + } + } + + /** + * @brief Deallocates the thread ID from the clients + * + * @param LocalThreadID + * + * @return true is returned if we are the last thread to deallocate + */ + bool DeallocateThreadID(uint32_t LocalThreadID) { + uint32_t ThreadIDMask = (1 << LocalThreadID); + while (true) { + uint32_t OriginalIDMask = HeartBeatEvents->NumClients.load(); + uint32_t NewIDMask = OriginalIDMask & ~ThreadIDMask; + if (HeartBeatEvents->NumClients.compare_exchange_strong(OriginalIDMask, NewIDMask)) { + // We set the atomic. If the new mask is zero then we were the last to deallocate + return NewIDMask == 0; + } + else { + // Failed to deallocate, try again + } + } + } + + struct HeartbeatClient { + std::atomic Type; // 1 = Server, 2 = Client + std::atomic HeartbeatCounter; + }; + + struct SHMHeartbeatEvent { + std::atomic Allocated; + std::atomic NumClients; + HeartbeatClient Clients[0]; + }; + std::string EventName; + std::unique_ptr SHM; + std::unique_ptr SHMRegion; + SHMHeartbeatEvent *HeartBeatEvents; + + bool Running{true}; + uint32_t ThreadID; + std::thread HeartBeatThread; + + const std::chrono::milliseconds HeartBeatRate = std::chrono::seconds(1); +}; + +int main(int argc, char **argv) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + auto Args = FEX::ArgLoader::Get(); + + FEX::Config::Value CoreConfig{"Core", 0}; + FEX::Config::Value ConfigIPCClient{"IPCClient", false}; + FEX::Config::Value ConfigELFType{"ELFType", false}; + FEX::Config::Value ConfigIPCID{"IPCID", "0"}; + + char File[256]{}; + + std::unique_ptr StateMessage; + std::unique_ptr StateFile; + std::unique_ptr HostEvent; + std::unique_ptr ClientEvent; + std::unique_ptr QuitFlag; + + // Heartbeat is the only thing robust enough to support client or server starting in any order + printf("Initializing Heartbeat\n"); fflush(stdout); + IPCHeartbeat HeartBeat(ConfigIPCID() + "IPCHeart_Lockstep", ConfigIPCClient()); + + printf("Client? %s\n", ConfigIPCClient() ? "Yes" : "No"); fflush(stdout); + + if (ConfigIPCClient()) { + if (!HeartBeat.WaitForServer()) { + // Server managed to time out + LogMan::Msg::E("[CLIENT %s] Timed out waiting for server", ConfigIPCID().c_str()); + return -1; + } + + // Now that we know the server is online, create our objects + StateMessage = std::make_unique(ConfigIPCID() + "IPCState_Lockstep", ConfigIPCClient(), sizeof(FEXCore::Core::CPUState), 1); + StateFile = std::make_unique(ConfigIPCID() + "IPCFile_Lockstep", ConfigIPCClient(), 256, 1); + HostEvent = std::make_unique(ConfigIPCID() + "IPCHost_Lockstep", ConfigIPCClient()); + ClientEvent = std::make_unique(ConfigIPCID() + "IPCClient_Lockstep", ConfigIPCClient()); + QuitFlag = std::make_unique(ConfigIPCID() + "IPCFlag_Lockstep", ConfigIPCClient()); + + HeartBeat.EnableClient(); + HostEvent->Wait(); + StateFile->Recv(File, 256); + } + else { + LogMan::Throw::A(!Args.empty(), "[SERVER %s] Not enough arguments", ConfigIPCID().c_str()); + strncpy(File, Args[0].c_str(), 256); + + // Create all of our objects now + StateMessage = std::make_unique(ConfigIPCID() + "IPCState_Lockstep", ConfigIPCClient(), sizeof(FEXCore::Core::CPUState), 1); + StateFile = std::make_unique(ConfigIPCID() + "IPCFile_Lockstep", ConfigIPCClient(), 256, 1); + HostEvent = std::make_unique(ConfigIPCID() + "IPCHost_Lockstep", ConfigIPCClient()); + ClientEvent = std::make_unique(ConfigIPCID() + "IPCClient_Lockstep", ConfigIPCClient()); + QuitFlag = std::make_unique(ConfigIPCID() + "IPCFlag_Lockstep", ConfigIPCClient()); + + HeartBeat.EnableServer(); + HeartBeat.WaitForClient(); + StateFile->Send(File, strlen(File)); + HostEvent->NotifyAll(); + } + + bool ShowProgress = false; + uint64_t PCEnd; + uint64_t PCStart = 1; + auto LastTime = std::chrono::high_resolution_clock::now(); + + FEXCore::Context::InitializeStaticTables(); + auto SHM = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + auto CTX = FEXCore::Context::CreateNewContext(); + FEXCore::Context::InitializeContext(CTX); + + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_DEFAULTCORE, CoreConfig() > 3 ? FEXCore::Config::CONFIG_CUSTOM : CoreConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, 1); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MAXBLOCKINST, 1); + + if (CoreConfig() == 4) { + FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory); + } + + FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); + + if (ConfigELFType()) { + FEX::HarnessHelper::ELFCodeLoader Loader{File, {}}; + bool Result = FEXCore::Context::InitCore(CTX, &Loader); + printf("Did we Load? %s\n", Result ? "Yes" : "No"); + } + else { + FEX::HarnessHelper::HarnessCodeLoader Loader{File, nullptr}; + bool Result = FEXCore::Context::InitCore(CTX, &Loader); + printf("Did we Load? %s\n", Result ? "Yes" : "No"); + ShowProgress = true; + PCEnd = Loader.GetFinalRIP(); + PCStart = Loader.DefaultRIP(); + } + + FEXCore::Core::CPUState State1; + bool MaskFlags = true; + bool MaskGPRs = false; + uint64_t LastRIP = 0; + + auto PrintProgress = [&](bool PrintFinal = false) { + if (ShowProgress) { + auto CurrentTime = std::chrono::high_resolution_clock::now(); + auto Diff = CurrentTime - LastTime; + if (Diff >= std::chrono::seconds(1) || PrintFinal) { + LastTime = CurrentTime; + uint64_t CurrentRIP = State1.rip - PCStart; + uint64_t EndRIP = PCEnd - PCStart; + double Progress = static_cast(CurrentRIP) / static_cast(EndRIP) * 25.0; + printf("Progress: ["); + for (uint32_t i = 0; i < 25; ++i) { + printf("%c", (Progress > static_cast(i)) ? '#' : '|'); + } + printf("] RIP: 0x%lx\n", State1.rip); + } + } + }; + + uint32_t ErrorLocation = 0; + bool Done = false; + bool ServerTimedOut = false; + while (!Done) + { + if (MaskFlags) { + // We need to reset the CPU flags to somethign standard so we don't need to handle flags in this case + FEXCore::Core::CPUState CPUState; + FEXCore::Context::GetCPUState(CTX, &CPUState); + for (int i = 0; i < 32; ++i) { + CPUState.flags[i] = 0; + } + CPUState.flags[1] = 1; // Default state + FEXCore::Context::SetCPUState(CTX, &CPUState); + } + + FEXCore::Context::ExitReason ExitReason; + ExitReason = FEXCore::Context::RunLoop(CTX, true); + FEXCore::Context::GetCPUState(CTX, &State1); + + if (ExitReason == FEXCore::Context::ExitReason::EXIT_SHUTDOWN) { + Done = true; + } + else if (ExitReason == FEXCore::Context::ExitReason::EXIT_UNKNOWNERROR) { + QuitFlag->GetFlag()->Set(); + ErrorLocation = -2; + break; + } + + if (!ConfigIPCClient()) { + FEXCore::Core::CPUState State2; + if (StateMessage->Recv(&State2, sizeof(FEXCore::Core::CPUState)) == 0) { + // We had a time out + // This can happen when the client managed to early exit + LogMan::Msg::E("Client Timed out"); + ErrorLocation = -2; + QuitFlag->GetFlag()->Set(); + ClientEvent->NotifyAll(); + break; + } + + PrintProgress(); + + uint64_t MatchMask = (1ULL << 36) - 1; + if (MaskFlags) { + MatchMask &= ~(1ULL << 35); // Remove FLAGS for now + } + + if (MaskGPRs) { + MatchMask &= ~((1ULL << 35) - 1); + } + + bool Matches = FEX::HarnessHelper::CompareStates(State1, State2, MatchMask, true); + + if (!Matches) { + LogMan::Msg::E("[SERVER %s] Stated ended up different at RIPS 0x%lx - 0x%lx - LastRIP: 0x%lx\n", ConfigIPCID().c_str(), State1.rip, State2.rip, LastRIP); + ErrorLocation = -3; + QuitFlag->GetFlag()->Set(); + break; + } + LastRIP = State1.rip; + ClientEvent->NotifyAll(); + } + else { + StateMessage->Send(&State1, sizeof(FEXCore::Core::CPUState)); + if (!ClientEvent->WaitFor(boost::get_system_time() + boost::posix_time::seconds(10))) { + // Timed out + LogMan::Msg::E("Server timed out"); + QuitFlag->GetFlag()->Set(); + ErrorLocation = -1; + ServerTimedOut = true; + } + } + + if (QuitFlag->GetFlag()->Load()) { + break; + } + } + + // Some cleanup that needs to be done in case some side failed + if (!ConfigIPCClient()) { + ClientEvent->NotifyAll(); + } + else { + if (!ServerTimedOut) { + // Send the latest state to the server so it can die + StateMessage->Send(&State1, sizeof(FEXCore::Core::CPUState)); + } + } + + FEXCore::Context::GetCPUState(CTX, &State1); + PrintProgress(true); + + if (ErrorLocation == 0) { + // One final check to make sure the RIP ended up in the correct location + if (State1.rip != PCEnd) { + ErrorLocation = State1.rip; + } + } + + if (ErrorLocation != 0) { + LogMan::Msg::E("Error Location: 0x%lx", ErrorLocation); + } + + FEXCore::Context::DestroyContext(CTX); + FEXCore::SHM::DestroyRegion(SHM); + FEX::Config::Shutdown(); + return ErrorLocation != 0; +} + +#include diff --git a/Source/Tests/TestHarness.cpp b/Source/Tests/TestHarness.cpp new file mode 100644 index 0000000000..02627cfbec --- /dev/null +++ b/Source/Tests/TestHarness.cpp @@ -0,0 +1,112 @@ +#include "Common/ArgumentLoader.h" +#include "CommonCore/VMFactory.h" +#include "HarnessHelpers.h" +#include "LogManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + default: + CharLevel = "???"; + break; + } + printf("[%s] %s\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); +} + +int main(int argc, char **argv) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + + FEX::ArgLoader::Load(argc, argv); + + auto Args = FEX::ArgLoader::Get(); + + LogMan::Throw::A(Args.size() > 1, "Not enough arguments"); + + FEXCore::Context::InitializeStaticTables(); + auto SHM1 = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + auto CTX1 = FEXCore::Context::CreateNewContext(); + + auto SHM2 = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + auto CTX2 = FEXCore::Context::CreateNewContext(); + + FEXCore::Context::SetCustomCPUBackendFactory(CTX1, VMFactory::CPUCreationFactory); + FEXCore::Config::SetConfig(CTX1, FEXCore::Config::CONFIG_DEFAULTCORE, FEXCore::Config::CONFIG_CUSTOM); + FEXCore::Config::SetConfig(CTX1, FEXCore::Config::CONFIG_SINGLESTEP, 1); + FEXCore::Config::SetConfig(CTX1, FEXCore::Config::CONFIG_MAXBLOCKINST, 1); + + FEXCore::Context::AddGuestMemoryRegion(CTX1, SHM1); + + FEXCore::Config::SetConfig(CTX2, FEXCore::Config::CONFIG_DEFAULTCORE, FEXCore::Config::CONFIG_INTERPRETER); + FEXCore::Config::SetConfig(CTX2, FEXCore::Config::CONFIG_SINGLESTEP, 1); + FEXCore::Config::SetConfig(CTX2, FEXCore::Config::CONFIG_MAXBLOCKINST, 1); + + FEXCore::Context::AddGuestMemoryRegion(CTX2, SHM2); + + FEXCore::Context::InitializeContext(CTX1); + FEXCore::Context::InitializeContext(CTX2); + + FEX::HarnessHelper::HarnessCodeLoader Loader{Args[0], Args[1].c_str()}; + + bool Result1 = FEXCore::Context::InitCore(CTX1, &Loader); + bool Result2 = FEXCore::Context::InitCore(CTX2, &Loader); + + if (!Result1 || !Result2) + return 2; + + while (FEXCore::Context::RunLoop(CTX1, true) == FEXCore::Context::ExitReason::EXIT_DEBUG); + LogMan::Msg::I("Running Core2"); + while (FEXCore::Context::RunLoop(CTX2, true) == FEXCore::Context::ExitReason::EXIT_DEBUG); + + FEXCore::Core::CPUState State1; + FEXCore::Core::CPUState State2; + + FEXCore::Context::GetCPUState(CTX1, &State1); + FEXCore::Context::GetCPUState(CTX2, &State2); + + bool Passed = Loader.CompareStates(State1, State2); + LogMan::Msg::I("Passed? %s\n", Passed ? "Yes" : "No"); + + FEXCore::SHM::DestroyRegion(SHM1); + FEXCore::Context::DestroyContext(CTX1); + FEXCore::SHM::DestroyRegion(SHM2); + FEXCore::Context::DestroyContext(CTX2); + + return Passed ? 0 : 1; +} + diff --git a/Source/Tests/TestHarnessRunner.cpp b/Source/Tests/TestHarnessRunner.cpp new file mode 100644 index 0000000000..60f9e94c6e --- /dev/null +++ b/Source/Tests/TestHarnessRunner.cpp @@ -0,0 +1,96 @@ +#include "Common/ArgumentLoader.h" +#include "CommonCore/VMFactory.h" +#include "HarnessHelpers.h" +#include "LogManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + default: + CharLevel = "???"; + break; + } + printf("[%s] %s\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); +} + +int main(int argc, char **argv) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + FEX::Config::Value CoreConfig{"Core", 0}; + FEX::Config::Value BlockSizeConfig{"MaxInst", 1}; + FEX::Config::Value SingleStepConfig{"SingleStep", false}; + FEX::Config::Value MultiblockConfig{"Multiblock", false}; + + auto Args = FEX::ArgLoader::Get(); + + LogMan::Throw::A(Args.size() > 1, "Not enough arguments"); + + FEXCore::Context::InitializeStaticTables(); + auto SHM = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + auto CTX = FEXCore::Context::CreateNewContext(); + + FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_DEFAULTCORE, CoreConfig() > 3 ? FEXCore::Config::CONFIG_CUSTOM : CoreConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MULTIBLOCK, MultiblockConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, SingleStepConfig()); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MAXBLOCKINST, BlockSizeConfig()); + FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory); + + FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); + + FEXCore::Context::InitializeContext(CTX); + + FEX::HarnessHelper::HarnessCodeLoader Loader{Args[0], Args[1].c_str()}; + + bool Result1 = FEXCore::Context::InitCore(CTX, &Loader); + + if (!Result1) + return 1; + + while (FEXCore::Context::RunLoop(CTX, true) == FEXCore::Context::ExitReason::EXIT_DEBUG) + ; + + FEXCore::SHM::DestroyRegion(SHM); + FEXCore::Context::DestroyContext(CTX); + + return 0; +} + diff --git a/Source/Tests/TestSingleStepHardware.cpp b/Source/Tests/TestSingleStepHardware.cpp new file mode 100644 index 0000000000..484a7f04e1 --- /dev/null +++ b/Source/Tests/TestSingleStepHardware.cpp @@ -0,0 +1,113 @@ +#include "Common/ArgumentLoader.h" +#include "Common/Config.h" +#include "ELFLoader.h" +#include "HarnessHelpers.h" +#include "LogManager.h" + +#include +#include +#include +#include +#include + + +namespace FEX { +uint8_t data[] = { + 0xb8, 0x00, 0x00, 0x80, 0x3f, // mov eax, 0x3f800000 + 0x0f, 0xc7, 0xf8, // rdseed eax + 0xba, 0x00, 0x00, 0x00, 0xe0, // mov edx, 0xe0000000 + 0x89, 0x02, // mov dword [edx], eax + 0xC3, // RET +}; + + class TestCodeLoader final : public FEXCore::CodeLoader { + static constexpr uint32_t PAGE_SIZE = 4096; + public: + + TestCodeLoader() = default; + + uint64_t StackSize() const override { + return STACK_SIZE; + } + + uint64_t SetupStack([[maybe_unused]] void *HostPtr, uint64_t GuestPtr) const override { + return GuestPtr + STACK_SIZE - 8; + } + + uint64_t DefaultRIP() const override { + return RIP; + } + + MemoryLayout GetLayout() const override { + uint64_t CodeSize = 0x500; + CodeSize = AlignUp(CodeSize, PAGE_SIZE); + return std::make_tuple(0, CodeSize, CodeSize); + } + + void MapMemoryRegion(std::function Mapper) override { + // XXX: Pull this from the config + Mapper(0xe000'0000, 0x1000'0000); + Mapper(0x2'0000'0000, 0x1'0000'1000); + } + + void LoadMemory(MemoryWriter Writer) override { + Writer(data, DefaultRIP(), sizeof(data)); + } + + private: + constexpr static uint64_t STACK_SIZE = PAGE_SIZE; + // Zero is special case to know when we are done + constexpr static uint64_t RIP = 0x1; + }; +} + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + default: + CharLevel = "???"; + break; + } + printf("[%s] %s\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); +} + +int main(int argc, char **argv) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + FEX::Config::Value CoreConfig{"Core", 0}; + auto Args = FEX::ArgLoader::Get(); + +// FEX::Core localCore {static_cast(CoreConfig())}; +// FEX::TestCodeLoader Loader{}; +// bool Result = localCore.Load(&Loader); +// +// auto Base = localCore.GetCPU()->MemoryMapper->GetPointer(0); +// localCore.RunLoop(true); +// printf("Managed to load? %s\n", Result ? "Yes" : "No"); + + FEX::Config::Shutdown(); + return 0; +} diff --git a/Source/Tests/UnitTestGenerator.cpp b/Source/Tests/UnitTestGenerator.cpp new file mode 100644 index 0000000000..6d27edafc2 --- /dev/null +++ b/Source/Tests/UnitTestGenerator.cpp @@ -0,0 +1,2440 @@ +#include "Common/ArgumentLoader.h" +#include "Common/Config.h" + +#include "LogManager.h" +#include +#include +#include +#include +#include + +constexpr std::array, 3> Disp8Ranges = {{ + {static_cast(-16), 16}, + {static_cast(-128), static_cast(-112)}, + {96, 112}, +}}; + +constexpr std::array, 1> Disp32Ranges = {{ + {0, 32}, +}}; + +int DumpThreshhold = (1 << 15); + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + default: + CharLevel = "???"; + break; + } + printf("[%s] %s\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + printf("[ASSERT] %s\n", Message); +} + +uint32_t GetModRMMapping(uint32_t Register) { + switch (Register) { + case FEXCore::X86State::REG_RCX: Register = 0b001; break; + case FEXCore::X86State::REG_RDX: Register = 0b010; break; + case FEXCore::X86State::REG_RBX: Register = 0b011; break; + case FEXCore::X86State::REG_RSP: Register = 0b100; break; + case FEXCore::X86State::REG_RBP: Register = 0b101; break; + case FEXCore::X86State::REG_RSI: Register = 0b110; break; + case FEXCore::X86State::REG_RDI: Register = 0b111; break; + default: return Register; break; // Default mapping + } + return Register; +}; + +auto OpToIndex = [](uint8_t Op) constexpr -> uint8_t { + switch (Op) { + // Group 1 + case 0x80: return 0; + case 0x81: return 1; + case 0x82: return 2; + case 0x83: return 3; + // Group 2 + case 0xC0: return 0; + case 0xC1: return 1; + case 0xD0: return 2; + case 0xD1: return 3; + case 0xD2: return 4; + case 0xD3: return 5; + // Group 3 + case 0xF6: return 0; + case 0xF7: return 1; + // Group 4 + case 0xFE: return 0; + // Group 5 + case 0xFF: return 0; + // Group 11 + case 0xC6: return 0; + case 0xC7: return 1; + } + return 0; +}; + +auto PrimaryIndexToOp = [](uint16_t Op) constexpr -> uint32_t { +#define OPD(group, prefix, Reg) ((((group) - FEXCore::X86Tables::TYPE_GROUP_1) << 6) | (prefix) << 3 | (Reg)) + switch (Op & ~0b111) { + // Group 1 + case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 0): return 0x800000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 0): return 0x810000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x82), 0): return 0x820000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 0): return 0x830000 | (Op & 0b111); + // Group 2 + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 0): return 0xC00000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 0): return 0xC10000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 0): return 0xD00000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 0): return 0xD10000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 0): return 0xD20000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 0): return 0xD30000 | (Op & 0b111); + // Group 3 + case OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 0): return 0xF60000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 0): return 0xF70000 | (Op & 0b111); + // Group 4 + case OPD(FEXCore::X86Tables::TYPE_GROUP_4, OpToIndex(0xFE), 0): return 0xFE0000 | (Op & 0b111); + // Group 5 + case OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 0): return 0xFF0000 | (Op & 0b111); + // Group 11 + case OPD(FEXCore::X86Tables::TYPE_GROUP_11, OpToIndex(0xC6), 0): return 0xC60000 | (Op & 0b111); + case OPD(FEXCore::X86Tables::TYPE_GROUP_11, OpToIndex(0xC7), 0): return 0xC70000 | (Op & 0b111); + } +#undef OPD + return 0; +}; + +auto SecondaryIndexToOp = [](uint16_t Op) constexpr -> uint32_t { + constexpr std::array GroupToOp = { + 0x000000, // 6 + 0x010000, // 7 + 0xBA0000, // 8 + 0xC70000, // 9 + 0xB90000, // 10 + 0x710000, // 12 (11 is part of the primary op table + 0x720000, // 13 + 0x730000, // 14 + 0xAE0000, // 15 + 0x180000, // 16 + 0x780000, // 17 + 0x0D0000, // P + }; + constexpr std::array PrefixToOp = { + 0, + 0xF300, + 0x6600, + 0xF200, + }; + return GroupToOp[Op >> 5] | PrefixToOp[(Op >> 3) & 0b11] | (Op & 0b111); +}; + +uint32_t GetModRMMappingXMM(uint32_t Register) { + return Register - FEXCore::X86State::REG_XMM_0; +}; + +static std::vector Code; +static std::string Filepath; +static std::string CurrentPrefix; +static int Step{}; +static int TimeSinceLastDump{}; + +void GenerateMove(uint32_t Register, uint64_t Literal) { + Register = GetModRMMapping(Register); + int Size = !!(Literal & (~0ULL << 32)) ? 8 : 4; + uint8_t REX = 0x40 | (Size == 8 ? 0b1000 : 0); + REX |= (Register & 0b1000) >> 3; + Code.emplace_back(REX); // REX + Code.emplace_back(0xB8 + (Register & 0b0111)); // MOV + for (int i = 0; i < Size; ++i) + Code.emplace_back(Literal >> (i * 8)); +} + +void Dump(std::string const &NameSuffix = "") { + { + // Dump a HLT at the end of the code + Code.emplace_back(0xF4); + } + printf("Size: %zd Inst: %d\n", Code.size(), TimeSinceLastDump); + std::string Filename = Filepath + "/" + CurrentPrefix + "_" + std::to_string(Step) + "_" + NameSuffix + ".raw"; + FILE *fp = fopen(Filename.c_str(), "wbe"); + fwrite(&Code[0], 1, Code.size(), fp); + fclose(fp); + Code.clear(); + Code.reserve(4096 * 128); + ++Step; + TimeSinceLastDump = 0; +} + +void GeneratePrimaryTable() { + using namespace FEXCore::X86Tables; + int numInst {}; + Step = 0; + TimeSinceLastDump = 0; + CurrentPrefix = "Primary"; + + bool AddressSizePrefix = false; + auto DoNormalOps = [&](const char *NameSuffix, auto Inserter, std::optional> ModRMInserter, uint8_t REX = 0) { + for (size_t OpIndex = 0; OpIndex < (sizeof(BaseOps) / sizeof(BaseOps[0])); ++OpIndex) { + auto &Op = BaseOps[OpIndex]; + if (Op.Type == TYPE_INST) { + if (Op.Flags & InstFlags::FLAGS_SETS_RIP || + Op.Flags & InstFlags::FLAGS_BLOCK_END) { + continue; + } + + if (OpIndex == 0x8D) { // LEA + // Special case LEA + // LEA with source as a register is INVALID and causes test harness generation to fail + continue; + } + + // Need to specialize these + if (Op.Flags & InstFlags::FLAGS_MEM_OFFSET || + Op.Flags & InstFlags::FLAGS_DEBUG_MEM_ACCESS || + Op.Flags & InstFlags::FLAGS_DEBUG) { + continue; + } + + if (Op.Flags & InstFlags::FLAGS_MODRM && + !ModRMInserter.has_value()) { + continue; + } + + Inserter(); + Code.push_back(OpIndex); + + if (Op.Flags & InstFlags::FLAGS_MODRM) { + ModRMInserter.value()(); + } + + if (Op.MoreBytes != 0) { + uint32_t MoreBytes = Op.MoreBytes; + + if (REX & 0b1000) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_MUL_2) { + MoreBytes <<= 1; + } + } + if (AddressSizePrefix) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_DIV_2) { + MoreBytes >>= 1; + } + } + + constexpr uint64_t Constant = 0xDEADBEEFBAD0DAD1ULL; + for (uint32_t i = 0; i < MoreBytes; ++i) { + Code.push_back(Constant >> (i * 8)); + } + } + numInst++; +#ifndef NDEBUG + Op.NumUnitTestsGenerated++; +#endif + + TimeSinceLastDump++; + if (TimeSinceLastDump >= DumpThreshhold) { + Dump(NameSuffix); + } + } + } + }; + + auto EmptyInserter = [](){}; + DoNormalOps("", EmptyInserter, {}); + Dump(); + + for (uint8_t REX = 0x40; REX < 0x50; ++REX) { + auto Inserter = [REX]() { + Code.push_back(REX); + }; + DoNormalOps("", Inserter, {}, REX); + } + Dump(); + + for (uint8_t Prefix = 0x64; Prefix < 0x66; ++Prefix) { + auto Inserter = [Prefix]() { + Code.push_back(Prefix); + }; + DoNormalOps("", Inserter, {}); + } + Dump(); + + AddressSizePrefix = true; + for (uint8_t Prefix = 0x66; Prefix < 0x67; ++Prefix) { + auto Inserter = [Prefix]() { + Code.push_back(Prefix); + }; + DoNormalOps("", Inserter, {}); + } + AddressSizePrefix = false; + for (uint8_t Prefix = 0x67; Prefix < 0x68; ++Prefix) { + auto Inserter = [Prefix]() { + Code.push_back(Prefix); + }; + DoNormalOps("", Inserter, {}); + } + + Dump(); + + for (uint8_t REX = 0x40; REX < 0x50; ++REX) { + for (uint8_t Prefix = 0x64; Prefix < 0x66; ++Prefix) { + auto Inserter = [REX, Prefix]() { + Code.push_back(Prefix); + Code.push_back(REX); + }; + DoNormalOps("", Inserter, {}, REX); + } + } + Dump(); + + // 00 = register direct + // - rm = 100 = SIB - Supported + // - rm = 101 = disp32 + // 01 = Register direct + displacement8 + // - rm = 100 = SIB + disp8 - Supported + // 10 = register direct + displacement32 + // - rm = 100 = SIB + disp32 - Supported + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + DoNormalOps("ModRM", EmptyInserter, Inserter); + } + } + Dump("ModRM"); + } + + // For register direct we need to load a memory region in to a register + // Memory region is at [0xe000'0000, 0xf000'0000] + // Drop ourselves right in the middle for testing + { + uint8_t ModRM_mod = 0b00; + const std::vector Registers = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // mod=00 = RIP relative addressing + // FEXCore::X86State::REG_RSP, // SIB + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // mod=00 = RIP RELATIVE. Ignores bit in REX + // FEXCore::X86State::REG_R13, // SIB. Ignores bit in REX + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto RM : Registers) { + uint8_t ModRM_rm = GetModRMMapping(RM); + + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + (ModRM_rm & 0b111); + auto PreInserter = [ModRM_rm, RM]() { + GenerateMove(RM, 0xe000'0000 + 0x800'0000); + + if (ModRM_rm & 0b1000) { + // We need REX for this + uint8_t REX = 0x40; + REX |= (ModRM_rm & 0b1000) >> 3; + Code.emplace_back(REX); // REX + } + + }; + + auto Inserter = [ModRM]() { + // Before we do anything, set this register to our memory region + Code.push_back(ModRM); + }; + DoNormalOps("ModRM", PreInserter, Inserter); + } + } + Dump("ModRM"); + } + + // For register indirect we need to load a memory region in to a register + // Memory region is at [0xe000'0000, 0xf000'0000] + // Drop ourselves right in the middle for testing + // Displacement is only 8bit here + { + uint8_t ModRM_mod = 0b01; + const std::vector Registers = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto RM : Registers) { + uint8_t ModRM_rm = GetModRMMapping(RM); + + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + (ModRM_rm & 0b111); + auto PreInserter = [ModRM_rm, RM]() { + GenerateMove(RM, 0xe000'0000 + 0x800'0000); + + if (ModRM_rm & 0b1000) { + // We need REX for this + uint8_t REX = 0x40; + REX |= (ModRM_rm & 0b1000) >> 3; + Code.emplace_back(REX); // REX + } + + }; + + auto Inserter = [ModRM, disp8]() { + // Before we do anything, set this register to our memory region + Code.push_back(ModRM); + // Disp8 bit follows ModRM + Code.push_back(disp8); + }; + DoNormalOps("ModRM", PreInserter, Inserter); + } + } + } + } + Dump("ModRM"); + } + + { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_RSP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R12, + // FEXCore::X86State::REG_R13, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + uint8_t ModRM_mod = 0b00; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + }; + DoNormalOps("ModRM_SIB", PreInserter, Inserter, REX); + } + } + } + } + Dump("ModRM_SIB"); + } + + { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b01; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp8]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + Code.push_back(disp8); + }; + DoNormalOps("ModRM_SIB_disp8", PreInserter, Inserter, REX); + } + } + } + } + } + } + Dump("ModRM_SIB_disp8"); + } + + { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b10; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0x2'0000'0000 + 0x0'8000'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp32]() { + int32_t disp = disp32; + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + // Disp32 bit follows SIB + for (int i = 0; i < 4; ++i) + Code.push_back(disp >> (i * 8)); + }; + DoNormalOps("ModRM_SIB_disp32", PreInserter, Inserter, REX); + } + } + } + } + } + } + Dump("ModRM_SIB_disp32"); + } + + // For register indirect we need to load a memory region in to a register + // Memory region is at [0xe000'0000, 0xf000'0000] + // Additional 4GB region is at [0x2'0000'0000, 0x3'0000'1000) + // Drop ourselves right in the middle for testing + // Displacement is 32bit here + { + uint8_t ModRM_mod = 0b10; + const std::vector Registers = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto RM : Registers) { + uint8_t ModRM_rm = GetModRMMapping(RM); + + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + (ModRM_rm & 0b111); + auto PreInserter = [ModRM_rm, RM]() { + GenerateMove(RM, 0x2'0000'0000 + 0x0'8000'0000); + + if (ModRM_rm & 0b1000) { + // We need REX for this + uint8_t REX = 0x40; + REX |= (ModRM_rm & 0b1000) >> 3; + Code.emplace_back(REX); // REX + } + + }; + + auto Inserter = [ModRM, disp32]() { + // Before we do anything, set this register to our memory region + Code.push_back(ModRM); + // Disp32 bit follows ModRM + for (int i = 0; i < 4; ++i) + Code.push_back(disp32 >> (i * 8)); + }; + DoNormalOps("ModRM", PreInserter, Inserter); + } + } + } + } + Dump("ModRM"); + } + + for (uint8_t REX = 0x40; REX < 0x50; ++REX) { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto REXInserter = [REX]() { + Code.push_back(REX); + }; + + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + DoNormalOps("ModRM", REXInserter, Inserter, REX); + } + } + } + Dump("ModRM"); + + printf("NumInsts: %d\n", numInst); +} + +void GeneratePrimaryGroupTable() { + using namespace FEXCore::X86Tables; + int numInst {}; + Step = 0; + TimeSinceLastDump = 0; + CurrentPrefix = "PrimaryGroup"; + + auto DoNormalOps = [&](const char *NameSuffix, auto SkipCheck, auto Inserter, auto &Table, std::optional> ModRMInserter, uint8_t REX = 0) { + for (size_t OpIndex = 0; OpIndex < (sizeof(Table) / sizeof(Table[0])); ++OpIndex) { + auto &Op = Table[OpIndex]; + if (Op.Type == TYPE_INST) { + if (Op.Flags & InstFlags::FLAGS_SETS_RIP || + Op.Flags & InstFlags::FLAGS_BLOCK_END) { + continue; + } + + if (SkipCheck(Op)) { + continue; + } + + // Need to specialize these + if (Op.Flags & InstFlags::FLAGS_MEM_OFFSET || + Op.Flags & InstFlags::FLAGS_DEBUG_MEM_ACCESS || + Op.Flags & InstFlags::FLAGS_DEBUG) { + continue; + } + + if (Op.Flags & InstFlags::FLAGS_MODRM && + !ModRMInserter.has_value()) { + continue; + } + +#ifndef NDEBUG + if (Op.DebugInfo.DebugFlags & FEXCore::X86Tables::X86InstDebugInfo::FLAGS_DIVIDE) { + continue; + } +#endif + + Inserter(); + uint32_t HexOp = PrimaryIndexToOp(OpIndex); + Code.push_back(HexOp >> 16); + + // Op selection is from the reg field of modrm + if (Op.Flags & InstFlags::FLAGS_MODRM) { + ModRMInserter.value()(HexOp & 0b111); + } + + if (Op.MoreBytes != 0) { + uint32_t MoreBytes = Op.MoreBytes; + + if (REX & 0b1000) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_MUL_2) { + MoreBytes <<= 1; + } + } + + constexpr uint64_t Constant = 0xDEADBEEFBAD0DAD1ULL; + for (uint32_t i = 0; i < MoreBytes; ++i) { + Code.push_back(Constant >> (i * 8)); + } + } + numInst++; +#ifndef NDEBUG + Op.NumUnitTestsGenerated++; +#endif + + TimeSinceLastDump++; + if (TimeSinceLastDump >= DumpThreshhold) { + Dump(NameSuffix); + } + } + } + }; + + auto EmptyInserter = [](){}; + + // 00 = register direct + // - rm = 100 = SIB - Supported + // - rm = 101 = disp32 + // 01 = Register direct + displacement8 + // - rm = 100 = SIB + disp8 - Supported + // 10 = register direct + displacement32 + // - rm = 100 = SIB + disp32 - Supported + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + auto Inserter = [ModRM](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_MEM_ONLY) { + return true; + } + return false; + }; + + DoNormalOps("ModRM", SkipCheck, EmptyInserter, PrimaryInstGroupOps, Inserter); + } + Dump("ModRM"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_RSP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R12, + // FEXCore::X86State::REG_R13, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + uint8_t ModRM_mod = 0b00; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + return true; + } + return false; + }; + + DoNormalOps(Name, SkipCheck, PreInserter, Table, Inserter, REX); + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB", PrimaryInstGroupOps); + } + { + auto SIBFunction = [&DoNormalOps](const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b01; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, &disp8](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + Code.push_back(disp8); + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + return true; + } + return false; + }; + + DoNormalOps(Name, SkipCheck, PreInserter, Table, Inserter, REX); + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB8", PrimaryInstGroupOps); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b10; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0x2'0000'0000 + 0x0'8000'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp32](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + // Disp32 bit follows SIB + for (int i = 0; i < 4; ++i) + Code.push_back(disp32 >> (i * 8)); + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + return true; + } + return false; + }; + DoNormalOps(Name, SkipCheck, PreInserter, Table, Inserter, REX); + + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB32", PrimaryInstGroupOps); + } + + printf("Primary Group NumInsts: %d\n", numInst); +} + +void GenerateSecondaryTable() { + using namespace FEXCore::X86Tables; + int numInst {}; + Step = 0; + TimeSinceLastDump = 0; + CurrentPrefix = "Secondary"; + + auto DoNormalOps = [&](const char *NameSuffix, auto SkipCheck, auto Inserter, std::optional> ModRMInserter, uint8_t REX = 0) { + for (size_t OpIndex = 0; OpIndex < (sizeof(SecondBaseOps) / sizeof(SecondBaseOps[0])); ++OpIndex) { + auto &Op = SecondBaseOps[OpIndex]; + if (Op.Type == TYPE_INST) { + if (Op.Flags & InstFlags::FLAGS_SETS_RIP || + Op.Flags & InstFlags::FLAGS_BLOCK_END) { + continue; + } + + if (SkipCheck(Op)) { + continue; + } + + // Need to specialize these + if (Op.Flags & InstFlags::FLAGS_MEM_OFFSET || + Op.Flags & InstFlags::FLAGS_DEBUG_MEM_ACCESS || + Op.Flags & InstFlags::FLAGS_DEBUG) { + continue; + } + + if (Op.Flags & InstFlags::FLAGS_MODRM && + !ModRMInserter.has_value()) { + continue; + } + + Inserter(); + Code.push_back(0x0F); // Escape op + Code.push_back(OpIndex); + + if (Op.Flags & InstFlags::FLAGS_MODRM) { + ModRMInserter.value()(); + } + + if (Op.MoreBytes != 0) { + uint32_t MoreBytes = Op.MoreBytes; + + if (REX & 0b1000) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_MUL_2) { + MoreBytes <<= 1; + } + } + + constexpr uint64_t Constant = 0xDEADBEEFBAD0DAD1ULL; + for (uint32_t i = 0; i < MoreBytes; ++i) { + Code.push_back(Constant >> (i * 8)); + } + } + numInst++; +#ifndef NDEBUG + Op.NumUnitTestsGenerated++; +#endif + + TimeSinceLastDump++; + if (TimeSinceLastDump >= DumpThreshhold) { + Dump(NameSuffix); + } + } + } + }; + + auto EmptyInserter = [](){}; + auto EmptySkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) { return false; }; + DoNormalOps("", EmptySkipCheck, EmptyInserter, {}); + Dump(); + + // 00 = register direct + // - rm = 100 = SIB - Supported + // - rm = 101 = disp32 + // 01 = Register direct + displacement8 + // - rm = 100 = SIB + disp8 - Supported + // 10 = register direct + displacement32 + // - rm = 100 = SIB + disp32 - Supported + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_MEM_ONLY) { + return true; + } + return false; + }; + + DoNormalOps("ModRM", SkipCheck, EmptyInserter, Inserter); + } + } + Dump("ModRM"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_RSP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R12, + // FEXCore::X86State::REG_R13, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + uint8_t ModRM_mod = 0b00; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + return true; + } + return false; + }; + + DoNormalOps(Name, SkipCheck, PreInserter, Inserter, REX); + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b01; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp8]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + Code.push_back(disp8); + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + // Skip if the modrm source is reg only + return true; + } + + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS) { + return true; + } + return false; + }; + DoNormalOps(Name, SkipRegOnly, PreInserter, Inserter, REX); + + } + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB8"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b10; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0x2'0000'0000 + 0x0'8000'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp32]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + int32_t udisp32 = static_cast(disp32); + // Disp32 bit follows SIB + for (int i = 0; i < 4; ++i) { + Code.push_back(udisp32 & 0xFF); + udisp32 >>= 8; + } + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + // Skip if the modrm source is reg only + return true; + } + + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS) { + return true; + } + return false; + }; + DoNormalOps(Name, SkipRegOnly, PreInserter, Inserter, REX); + + } + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB32"); + } + + printf("Secondary NumInsts: %d\n", numInst); +} + +void GenerateSecondaryGroupTable() { + using namespace FEXCore::X86Tables; + int numInst {}; + Step = 0; + TimeSinceLastDump = 0; + CurrentPrefix = "SecondaryGroup"; + + auto DoNormalOps = [&](const char *NameSuffix, auto SkipCheck, auto Inserter, std::optional> ModRMInserter, uint8_t REX = 0) { + for (size_t OpIndex = 0; OpIndex < (sizeof(SecondInstGroupOps) / sizeof(SecondInstGroupOps[0])); ++OpIndex) { + auto &Op = SecondInstGroupOps[OpIndex]; + if (Op.Type == TYPE_INST) { + if (Op.Flags & InstFlags::FLAGS_SETS_RIP || + Op.Flags & InstFlags::FLAGS_BLOCK_END) { + continue; + } + +#ifndef NDEBUG + if (Op.DebugInfo.DebugFlags & FEXCore::X86Tables::X86InstDebugInfo::FLAGS_DEBUG) { + continue; + } +#endif + + if (SkipCheck(Op)) { + continue; + } + + // Need to specialize these + if (Op.Flags & InstFlags::FLAGS_MEM_OFFSET || + Op.Flags & InstFlags::FLAGS_DEBUG_MEM_ACCESS || + Op.Flags & InstFlags::FLAGS_DEBUG) { + continue; + } + + if (Op.Flags & InstFlags::FLAGS_MODRM && + !ModRMInserter.has_value()) { + continue; + } + + Inserter(); + uint32_t HexOp = SecondaryIndexToOp(OpIndex); + + uint32_t SecondaryEscapeOp = (HexOp >> 8) & 0xFF; + if (SecondaryEscapeOp != 0) + Code.push_back(SecondaryEscapeOp); // Secondary escape op + Code.push_back(0x0F); // Escape op + Code.push_back((HexOp >> 16) & 0xFF); + + // Op selection is from the reg field of modrm + if (Op.Flags & InstFlags::FLAGS_MODRM) { + ModRMInserter.value()(HexOp & 0b111); + } + + if (Op.MoreBytes != 0) { + uint32_t MoreBytes = Op.MoreBytes; + + if (REX & 0b1000) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_MUL_2) { + MoreBytes <<= 1; + } + } + + constexpr uint64_t Constant = 0xDEADBEEFBAD0DAD1ULL; + for (uint32_t i = 0; i < MoreBytes; ++i) { + Code.push_back(Constant >> (i * 8)); + } + } + numInst++; +#ifndef NDEBUG + Op.NumUnitTestsGenerated++; +#endif + + TimeSinceLastDump++; + if (TimeSinceLastDump >= DumpThreshhold) { + Dump(NameSuffix); + } + } + } + }; + + auto EmptyInserter = [](){}; + + // 00 = register direct + // - rm = 100 = SIB - Supported + // - rm = 101 = disp32 + // 01 = Register direct + displacement8 + // - rm = 100 = SIB + disp8 - Supported + // 10 = register direct + displacement32 + // - rm = 100 = SIB + disp32 - Supported + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + auto Inserter = [ModRM](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_MEM_ONLY) { + return true; + } + return false; + }; + + DoNormalOps("ModRM", SkipCheck, EmptyInserter, Inserter); + } + Dump("ModRM"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_RSP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R12, + // FEXCore::X86State::REG_R13, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + uint8_t ModRM_mod = 0b00; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + }; + auto SkipCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + return true; + } + return false; + }; + + DoNormalOps(Name, SkipCheck, PreInserter, Inserter, REX); + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b01; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp8](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + Code.push_back(disp8); + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + // Skip if the modrm source is reg only + return true; + } + + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS) { + return true; + } + return false; + }; + DoNormalOps(Name, SkipRegOnly, PreInserter, Inserter, REX); + + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB8"); + } + + { + auto SIBFunction = [&DoNormalOps](const char *Name) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b10; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (0 << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase]() { + GenerateMove(RegBase, 0x2'0000'0000 + 0x0'8000'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + }; + + auto Inserter = [ModRM, &SIB, disp32](uint8_t reg_field) { + Code.push_back(ModRM | (reg_field << 3)); + Code.push_back(SIB); // SIB + // Disp32 bit follows SIB + for (int i = 0; i < 4; ++i) + Code.push_back(disp32 >> (i * 8)); + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY) { + // Skip if the modrm source is reg only + return true; + } + + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS) { + return true; + } + return false; + }; + DoNormalOps(Name, SkipRegOnly, PreInserter, Inserter, REX); + + } + } + } + } + } + Dump(Name); + }; + + SIBFunction("ModRM_SIB32"); + } + + printf("Secondary Group NumInsts: %d\n", numInst); +} + +void GenerateSSEInstructions() { + using namespace FEXCore::X86Tables; + int numInst {}; + Step = 0; + TimeSinceLastDump = 0; + CurrentPrefix = "SSE"; + + bool AddressSizePrefix = false; + + auto DoNormalOps = [&](const char *NameSuffix, auto SkipCheck, auto Inserter, auto &Table, std::optional> ModRMInserter, uint8_t REX = 0) { + for (size_t OpIndex = 0; OpIndex < (sizeof(Table) / sizeof(Table[0])); ++OpIndex) { + auto &Op = Table[OpIndex]; + if (Op.Type == TYPE_INST) { + if (Op.Flags & InstFlags::FLAGS_SETS_RIP || + Op.Flags & InstFlags::FLAGS_BLOCK_END) { + continue; + } + if (!(Op.Flags & InstFlags::FLAGS_XMM_FLAGS)) { + continue; + } + + if (SkipCheck(Op)) { + continue; + } + + // Need to specialize these + if (Op.Flags & InstFlags::FLAGS_MEM_OFFSET || + Op.Flags & InstFlags::FLAGS_DEBUG_MEM_ACCESS || + Op.Flags & InstFlags::FLAGS_DEBUG) { + continue; + } + + if (Op.Flags & InstFlags::FLAGS_MODRM && + !ModRMInserter.has_value()) { + continue; + } + + Inserter(); + Code.push_back(0x0F); // Need to escape to get in to this table + Code.push_back(OpIndex); + + if (Op.Flags & InstFlags::FLAGS_MODRM) { + ModRMInserter.value()(); + } + + if (Op.MoreBytes != 0) { + uint32_t MoreBytes = Op.MoreBytes; + + if (REX & 0b1000) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_MUL_2) { + MoreBytes <<= 1; + } + } + if (AddressSizePrefix) { + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_DISPLACE_SIZE_DIV_2) { + MoreBytes >>= 1; + } + } + + constexpr uint64_t Constant = 0xDEADBEEFBAD0DAD1ULL; + for (uint32_t i = 0; i < MoreBytes; ++i) { + Code.push_back(Constant >> (i * 8)); + } + } + numInst++; +#ifndef NDEBUG + Op.NumUnitTestsGenerated++; +#endif + + TimeSinceLastDump++; + if (TimeSinceLastDump >= DumpThreshhold) { + Dump(NameSuffix); + } + } + } + }; + + auto EmptyInserter = [](){}; + auto SkipMemOnlyCheck = [](FEXCore::X86Tables::X86InstInfo const &Op) + { + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM)) { + return true; + } + if (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_MEM_ONLY) { + return true; + } + return false; + }; + + + // 00 = register direct + // - rm = 100 = SIB - Supported + // - rm = 101 = disp32 + // 01 = Register direct + displacement8 + // - rm = 100 = SIB + disp8 - Supported + // 10 = register direct + displacement32 + // - rm = 100 = SIB + disp32 - Supported + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + + DoNormalOps("ModRM", SkipMemOnlyCheck, EmptyInserter, SecondBaseOps, Inserter); + } + } + Dump("ModRM"); + } + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + auto REPInserter = []() { + Code.push_back(0xF3); + }; + DoNormalOps("ModRM_REP", SkipMemOnlyCheck, REPInserter, RepModOps, Inserter); + } + } + Dump("ModRM_REP"); + } + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + auto REPInserter = []() { + Code.push_back(0xF2); + }; + DoNormalOps("ModRM_REPNE", SkipMemOnlyCheck, REPInserter, RepNEModOps, Inserter); + } + } + Dump("ModRM_REPNE"); + } + { + uint8_t ModRM_mod = 0b11; + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (uint8_t ModRM_rm = 0; ModRM_rm < 8; ++ModRM_rm) { + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + auto Inserter = [ModRM]() { + Code.push_back(ModRM); + }; + auto OpSizeInserter = []() { + Code.push_back(0x66); + }; + DoNormalOps("ModRM_OpSize", SkipMemOnlyCheck, OpSizeInserter, OpSizeModOps, Inserter); + } + } + Dump("ModRM_OpSize"); + } + + { + auto SIBFunction = [&DoNormalOps](uint8_t Prefix, const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + // FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_RSP, + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + FEXCore::X86State::REG_R12, + // FEXCore::X86State::REG_R13, // ModRM = 0b00 = base = 0 + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + uint8_t ModRM_mod = 0b00; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase, Prefix]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + if (Prefix != 0) { + Code.emplace_back(Prefix); + } + }; + + auto Inserter = [ModRM, &SIB]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + // Skip if the modrm source is reg only + return Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY; + }; + + DoNormalOps(Name, SkipRegOnly, PreInserter, Table, Inserter, REX); + } + } + } + } + Dump(Name); + }; + + SIBFunction(0, "ModRM_SIB", SecondBaseOps); + SIBFunction(0xF3, "ModRM_REP_SIB", RepModOps); + SIBFunction(0xF2, "ModRM_REPNE_SIB", RepNEModOps); + SIBFunction(0x66, "ModRM_OpSize_SIB", OpSizeModOps); + } + { + auto SIBFunction = [&DoNormalOps](uint8_t Prefix, const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b01; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp8Ranges) { + for(int16_t disp8 = DispRange.first; disp8 <= DispRange.second; disp8 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase, Prefix]() { + GenerateMove(RegBase, 0xe000'0000 + 0x800'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + if (Prefix != 0) { + Code.emplace_back(Prefix); + } + }; + + auto Inserter = [ModRM, &SIB, disp8]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + Code.push_back(disp8); + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + // Skip if the modrm source is reg only + return Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM && + Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY; + }; + + DoNormalOps(Name, SkipRegOnly, PreInserter, Table, Inserter, REX); + } + } + } + } + } + } + Dump(Name); + }; + + SIBFunction(0, "ModRM_SIB8", SecondBaseOps); + SIBFunction(0xF3, "ModRM_REP_SIB8", RepModOps); + SIBFunction(0xF2, "ModRM_REPNE_SIB8", RepNEModOps); + SIBFunction(0x66, "ModRM_OpSize_SIB8", OpSizeModOps); + } + + { + auto SIBFunction = [&DoNormalOps](uint8_t Prefix, const char *Name, auto &Table) { + const std::vector RegistersIndex = { + FEXCore::X86State::REG_RAX, + FEXCore::X86State::REG_RBX, + FEXCore::X86State::REG_RCX, + FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, + FEXCore::X86State::REG_RDI, + FEXCore::X86State::REG_RBP, + // FEXCore::X86State::REG_RSP, // RSP = Scale*Index = 0 + FEXCore::X86State::REG_R8, + FEXCore::X86State::REG_R9, + FEXCore::X86State::REG_R10, + FEXCore::X86State::REG_R11, + // FEXCore::X86State::REG_R12, // = 0 + FEXCore::X86State::REG_R13, + FEXCore::X86State::REG_R14, + FEXCore::X86State::REG_R15, + }; + + const std::vector RegistersBase = { + FEXCore::X86State::REG_RBP, // ModRM = 0b00 = base = 0 + }; + + uint8_t ModRM_mod = 0b10; + uint8_t ModRM_rm = 0b100; + for (uint32_t RegIndex : RegistersIndex) { + for (uint32_t RegBase : RegistersBase) { + for (uint8_t scale = 0; scale < 1; ++scale) { + for (uint8_t ModRM_reg = 0; ModRM_reg < 8; ++ModRM_reg) { + for (auto DispRange : Disp32Ranges) { + for(int64_t disp32 = DispRange.first; disp32 <= DispRange.second; disp32 += 16) { + if (RegIndex == RegBase) { + // Skip these + continue; + } + uint8_t IndexReg = GetModRMMapping(RegIndex); + uint8_t BaseReg = GetModRMMapping(RegBase); + uint8_t ModRM = (ModRM_mod << 6) | + (ModRM_reg << 3) | + ModRM_rm; + + uint8_t REX = 0x40; + REX |= (IndexReg & 0b1000) >> 2; + REX |= (BaseReg & 0b1000) >> 3; + + uint8_t index = IndexReg & 0b111; + uint8_t base = BaseReg & 0b111; + uint8_t SIB = + (scale << 6) | + (index << 3) | + base; + + auto PreInserter = [REX, RegIndex, RegBase, Prefix]() { + GenerateMove(RegBase, 0x2'0000'0000 + 0x0'8000'0000); + GenerateMove(RegIndex, 16); // Just ensure 128bit alignment + Code.emplace_back(REX); // REX + if (Prefix != 0) { + Code.emplace_back(Prefix); + } + }; + + auto Inserter = [ModRM, &SIB, disp32]() { + Code.push_back(ModRM); + Code.push_back(SIB); // SIB + // Disp32 bit follows SIB + for (int i = 0; i < 4; ++i) + Code.push_back(disp32 >> (i * 8)); + }; + auto SkipRegOnly = [](FEXCore::X86Tables::X86InstInfo const &Op) { + if ((Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_MODRM) && + (Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_SF_MOD_REG_ONLY)) { + // Skip if the modrm source is reg only + return true; + } + if (!(Op.Flags & FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS)) { + return true; + } + + return false; + }; + + DoNormalOps(Name, SkipRegOnly, PreInserter, Table, Inserter, REX); + } + } + } + } + } + } + Dump(Name); + }; + + SIBFunction(0, "ModRM_SIB32", SecondBaseOps); + SIBFunction(0xF3, "ModRM_REP_SIB32", RepModOps); + SIBFunction(0xF2, "ModRM_REPNE_SIB32", RepNEModOps); + SIBFunction(0x66, "ModRM_OpSize_SIB32", OpSizeModOps); + } + + printf("SSE NumInsts: %d\n", numInst); +} + +int main(int argc, char **argv) { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + auto Args = FEX::ArgLoader::Get(); + + LogMan::Throw::A(!Args.empty(), "Not enough arguments"); + + FEXCore::X86Tables::InitializeInfoTables(); + + Code.reserve(4096*128); + Filepath = Args[0]; + + GeneratePrimaryTable(); + GeneratePrimaryGroupTable(); + GenerateSecondaryTable(); + GenerateSecondaryGroupTable(); + GenerateSSEInstructions(); + +#ifndef NDEBUG + if (Args.size() > 1) { + using namespace FEXCore::X86Tables; + auto DumpTable = [](std::string const &Filepath, std::string const &TableName, auto &Table) { + std::string Filename = Filepath + "/" + TableName + ".csv"; + FILE *fp = fopen(Filename.c_str(), "wbe"); + + fprintf(fp, "HEX, Name, Num Times compiled\n"); + for (size_t OpIndex = 0; OpIndex < (sizeof(Table) / sizeof(Table[0])); ++OpIndex) { + auto &Op = Table[OpIndex]; + if (Op.Type == TYPE_INST) { + fprintf(fp, "0x%zx, %s, %d\n", OpIndex, Op.Name, Op.NumUnitTestsGenerated); + } + } + fclose(fp); + }; + + DumpTable(Args[1], "Primary", BaseOps); + DumpTable(Args[1], "Secondary", SecondBaseOps); + DumpTable(Args[1], "Secondary_REP", RepModOps); + DumpTable(Args[1], "Secondary_REPNE", RepNEModOps); + DumpTable(Args[1], "Secondary_OpSize", OpSizeModOps); + DumpTable(Args[1], "Primary_Groups", PrimaryInstGroupOps); + DumpTable(Args[1], "Secondary_Groups", SecondInstGroupOps); + DumpTable(Args[1], "SecondaryModRM", SecondModRMTableOps); + } +#endif + + return 0; +} + diff --git a/Source/Tools/CMakeLists.txt b/Source/Tools/CMakeLists.txt new file mode 100644 index 0000000000..cf2fcbae51 --- /dev/null +++ b/Source/Tools/CMakeLists.txt @@ -0,0 +1,10 @@ +add_subdirectory(Debugger/) + +set(NAME Opt) +set(SRCS Opt.cpp) + +add_executable(${NAME} ${SRCS}) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/SonicUtils/) + +target_link_libraries(${NAME} FEXCore Common CommonCore SonicUtils pthread LLVM) diff --git a/Source/Tools/Debugger/CMakeLists.txt b/Source/Tools/Debugger/CMakeLists.txt new file mode 100644 index 0000000000..9b095e22a3 --- /dev/null +++ b/Source/Tools/Debugger/CMakeLists.txt @@ -0,0 +1,29 @@ +set(NAME Debugger) +set(SRCS Main.cpp + DebuggerState.cpp + Context.cpp + FEXImGui.cpp + IMGui.cpp + IRLexer.cpp + GLUtils.cpp + MainWindow.cpp + Disassembler.cpp + ${CMAKE_SOURCE_DIR}/External/imgui/examples/imgui_impl_glfw.cpp + ${CMAKE_SOURCE_DIR}/External/imgui/examples/imgui_impl_opengl3.cpp + ) + +find_library(EPOXY_LIBRARY epoxy) +find_library(GLFW_LIBRARY glfw3) +find_package(LLVM CONFIG QUIET) + +add_definitions(-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM=) +add_executable(${NAME} ${SRCS}) + +target_link_libraries(${NAME} PRIVATE LLVM) +target_include_directories(${NAME} PRIVATE ${LLVM_INCLUDE_DIRS}) + +target_include_directories(${NAME} PRIVATE ${CMAKE_SOURCE_DIR}/Source/) +target_include_directories(${NAME} PRIVATE ${CMAKE_SOURCE_DIR}/External/SonicUtils/) +target_include_directories(${NAME} PRIVATE ${CMAKE_SOURCE_DIR}/External/imgui/examples/) + +target_link_libraries(${NAME} PRIVATE FEXCore Common CommonCore SonicUtils pthread LLVM epoxy glfw X11 EGL imgui tiny-json json-maker) diff --git a/Source/Tools/Debugger/Context.cpp b/Source/Tools/Debugger/Context.cpp new file mode 100644 index 0000000000..c059104e01 --- /dev/null +++ b/Source/Tools/Debugger/Context.cpp @@ -0,0 +1,90 @@ +#include "Context.h" + +#include +#include +#include + +namespace GLContext { +void glfw_error_callback(int error, const char* description) +{ + fprintf(stderr, "Glfw Error %d: %s\n", error, description); +} + +class GLFWContext final : public GLContext::Context { +public: + void Create(const char *Title) override { + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) { + assert(0 && "Couldn't init glfw"); + } + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_RED_BITS, 8); + glfwWindowHint(GLFW_GREEN_BITS, 8); + glfwWindowHint(GLFW_BLUE_BITS, 8); + glfwWindowHint(GLFW_ALPHA_BITS, 8); + glfwWindowHint(GLFW_RESIZABLE, 1); + glfwWindowHint(GLFW_DOUBLEBUFFER, 1); + + Window = glfwCreateWindow(640, 640, Title, nullptr, nullptr); + if (!Window) { + assert(0 && "Couldn't create window"); + } + glfwMakeContextCurrent(Window); + glfwSwapInterval(1); + } + + void Shutdown() override { + glfwMakeContextCurrent(nullptr); + glfwDestroyWindow(Window); + glfwTerminate(); + } + + void Swap() override { + glfwSwapBuffers(Window); + CheckWindowDimensions(); + } + + void RegisterResizeEvent(ResizeEvent Event) override { + ResizeEvents.emplace_back(Event); + } + + void GetDim(uint32_t *Dim) override { + Dim[0] = Width; + Dim[1] = Height; + } + +private: + + void CheckWindowDimensions() { + int LocalWidth; + int LocalHeight; + glfwGetWindowSize(Window, &LocalWidth, &LocalHeight); + + if (LocalHeight != Height || LocalWidth != Width) { + Width = LocalWidth; + Height = LocalHeight; + + for (auto const &Event : ResizeEvents) { + Event(Width, Height); + } + } + } + void* GetWindow() override { + return Window; + } + + std::vector ResizeEvents; + + GLFWwindow *Window; + uint32_t Width{}; + uint32_t Height{}; +}; + +std::unique_ptr CreateContext() { + return std::make_unique(); +} + +} diff --git a/Source/Tools/Debugger/Context.h b/Source/Tools/Debugger/Context.h new file mode 100644 index 0000000000..5b9a347228 --- /dev/null +++ b/Source/Tools/Debugger/Context.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace GLContext { +class Context { +public: + virtual ~Context() {} + virtual void Create(const char *Title) = 0; + virtual void Shutdown() = 0; + virtual void Swap() = 0; + virtual void GetDim(uint32_t *Dim) = 0; + + using ResizeEvent = std::function; + virtual void RegisterResizeEvent(ResizeEvent Event) = 0; + virtual void* GetWindow() = 0; +}; + +std::unique_ptr CreateContext(); +} diff --git a/Source/Tools/Debugger/DebuggerState.cpp b/Source/Tools/Debugger/DebuggerState.cpp new file mode 100644 index 0000000000..85f8ab7b95 --- /dev/null +++ b/Source/Tools/Debugger/DebuggerState.cpp @@ -0,0 +1,149 @@ +#include "DebuggerState.h" + +#include +#include + +namespace FEX::DebuggerState { + +FEXCore::Context::Context *s_CTX{}; +FEXCore::Config::ConfigCore s_CoreType = FEXCore::Config::ConfigCore::CONFIG_INTERPRETER; +int s_IsStepping = 0; +bool NewState = false; + +std::function StepCallback; +std::function PauseCallback; +std::function ContinueCallback; +std::function NewStateCallback; +std::function CloseCallback; +std::function CreateCallback; +std::function CompileRIPCallback; +std::function GetIRCallback; + +bool ActiveCore() { + return s_CTX != nullptr; +} + +void SetContext(FEXCore::Context::Context *ctx) { + s_CTX = ctx; +} + +FEXCore::Context::Context *GetContext() { + return s_CTX; +} + +FEXCore::Config::ConfigCore GetCoreType() { + return s_CoreType; +} + +void SetCoreType(FEXCore::Config::ConfigCore CoreType) { + s_CoreType = CoreType; +} + +int GetRunningMode() { + return s_IsStepping; +} + +int GetCoreCurrentRunningMode() { + if (ActiveCore()) { + return FEXCore::Config::GetConfig(s_CTX, FEXCore::Config::CONFIG_SINGLESTEP); + } + return s_IsStepping; +} + +void SetRunningMode(int RunningMode) { + s_IsStepping = RunningMode; + + if (ActiveCore()) { + FEXCore::Config::SetConfig(s_CTX, FEXCore::Config::CONFIG_SINGLESTEP, RunningMode); + } +} + +FEXCore::Core::CPUState GetCPUState() { + if (!ActiveCore()) { + return FEXCore::Core::CPUState{}; + } + return FEXCore::Context::Debug::GetCPUState(s_CTX); +} + +bool IsCoreRunning() { + if (!ActiveCore()) { + return false; + } + + return !FEXCore::Context::IsDone(s_CTX); +} + +// Client interface +void RegisterStepCallback(std::function Callback) { + StepCallback = std::move(Callback); +} + +void RegisterPauseCallback(std::function Callback) { + PauseCallback = std::move(Callback); +} + +void RegisterContinueCallback(std::function Callback) { + ContinueCallback = std::move(Callback); +} + +void Step() { + StepCallback(); +} + +void Pause() { + PauseCallback(); +} + +void Continue() { + ContinueCallback(); +} + +void RegisterCreateCallback(std::function Callback) +{ + CreateCallback = std::move(Callback); +} + +void Create(char const *Filename, bool ELF) { + CreateCallback(Filename, ELF); +} + +void RegisterCompileRIPCallback(std::function Callback) { + CompileRIPCallback = std::move(Callback); +} +void CompileRIP(uint64_t RIP) { + CompileRIPCallback(RIP); +} + +void RegisterCloseCallback(std::function Callback) { + CloseCallback = std::move(Callback); +} + +void Close() { + CloseCallback(); +} + +void RegisterNewStateCallback(std::function Callback) { + NewStateCallback = std::move(Callback); +} + +void RegisterGetIRCallback(std::function Callback) { + GetIRCallback = std::move(Callback); +} + +void GetIR(std::stringstream *out, uint64_t PC) { + GetIRCallback(out, PC); +} + +void CallNewState() { + NewStateCallback(); +} + +bool HasNewState() { + return NewState; +} + +void SetHasNewState(bool State) { + NewState = State; +} + +} diff --git a/Source/Tools/Debugger/DebuggerState.h b/Source/Tools/Debugger/DebuggerState.h new file mode 100644 index 0000000000..fe8e34a9c2 --- /dev/null +++ b/Source/Tools/Debugger/DebuggerState.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include + +namespace FEX::DebuggerState { +bool ActiveCore(); + +void SetContext(FEXCore::Context::Context *ctx); +FEXCore::Context::Context *GetContext(); + +FEXCore::Config::ConfigCore GetCoreType(); +void SetCoreType(FEXCore::Config::ConfigCore CoreType); + +int GetRunningMode(); ///< This is the running mode we've set +int GetCoreCurrentRunningMode(); ///< This typically matches `GetRunningMode()` but can differ when we are in the middle of stepping +void SetRunningMode(int RunningMode); + +FEXCore::Core::CPUState GetCPUState(); + +bool IsCoreRunning(); + +// Client interface +void RegisterStepCallback(std::function Callback); +void RegisterPauseCallback(std::function Callback); +void RegisterContinueCallback(std::function Callback); +void Step(); +void Pause(); +void Continue(); + +void RegisterCreateCallback(std::function Callback); +void Create(char const *Filename, bool ELF); + +void RegisterCompileRIPCallback(std::function Callback); +void CompileRIP(uint64_t RIP); + +void RegisterCloseCallback(std::function Callback); +void Close(); + +void RegisterNewStateCallback(std::function Callback); +void CallNewState(); + +void RegisterGetIRCallback(std::function); +void GetIR(std::stringstream *out, uint64_t PC); + +bool HasNewState(); +void SetHasNewState(bool State = true); +} diff --git a/Source/Tools/Debugger/Disassembler.cpp b/Source/Tools/Debugger/Disassembler.cpp new file mode 100644 index 0000000000..7f5aa4824d --- /dev/null +++ b/Source/Tools/Debugger/Disassembler.cpp @@ -0,0 +1,94 @@ +#include "Disassembler.h" +#include +#include +#include +#include +#include + +namespace FEX::Debugger { + +class LLVMDisassembler final : public Disassembler { +public: + ~LLVMDisassembler() override { + LLVMDisasmDispose(LLVMContext); + } + explicit LLVMDisassembler(const char *Arch); + std::string Disassemble(uint8_t *Code, uint32_t CodeSize, uint32_t MaxInst, uint64_t StartingPC, uint32_t *InstructionCount) override; + +private: + LLVMDisasmContextRef LLVMContext; + +}; + +LLVMDisassembler::LLVMDisassembler(const char *Arch) { + LLVMInitializeAllTargetInfos(); + LLVMInitializeAllTargetMCs(); + LLVMInitializeAllDisassemblers(); + + LLVMContext = LLVMCreateDisasmCPU(Arch, "", nullptr, 0, nullptr, nullptr); + + if (!LLVMContext) + return; + + LLVMSetDisasmOptions(LLVMContext, LLVMDisassembler_Option_AsmPrinterVariant | + LLVMDisassembler_Option_PrintLatency); +} + +std::string LLVMDisassembler::Disassemble(uint8_t *Code, uint32_t CodeSize, uint32_t MaxInst, uint64_t StartingPC, uint32_t *InstructionCount) { + std::ostringstream Output; + + uint8_t *CurrentRIPAddr = Code; + uint8_t *EndRIPAddr = Code + CodeSize; + uint64_t CurrentRIP = StartingPC; + uint32_t NumberOfInstructions = 0; + + while (CurrentRIPAddr <= EndRIPAddr) { + char OutputText[128]; + size_t InstSize = LLVMDisasmInstruction(LLVMContext, + CurrentRIPAddr, + static_cast(EndRIPAddr - CurrentRIPAddr), + CurrentRIP, + OutputText, + 128); + + Output << "0x" << std::hex << CurrentRIP << ": "; + if (!InstSize) { + Output << "" << std::endl; + break; + } + else { + // Print instruction hex encodings if we want + if (!true) { + for (size_t i = 0; i < InstSize; ++i) { + Output << std::setw(2) << std::setfill('0') << static_cast(CurrentRIPAddr[i]); + if ((i + 1) == InstSize) { + Output << ": "; + } + else { + Output << " "; + } + } + } + Output << OutputText << std::endl; + } + + CurrentRIP += InstSize; + CurrentRIPAddr += InstSize; + NumberOfInstructions++; + if (NumberOfInstructions >= MaxInst) { + break; + } + } + + *InstructionCount = NumberOfInstructions; + return Output.str(); +} + +std::unique_ptr CreateHostDisassembler() { + return std::make_unique("x86_64-none-unknown"); +} +std::unique_ptr CreateGuestDisassembler() { + return std::make_unique("x86_64-none-unknown"); +} + +} diff --git a/Source/Tools/Debugger/Disassembler.h b/Source/Tools/Debugger/Disassembler.h new file mode 100644 index 0000000000..86e0ec0cef --- /dev/null +++ b/Source/Tools/Debugger/Disassembler.h @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace FEX::Debugger { + +class Disassembler { +public: + virtual ~Disassembler() {} + virtual std::string Disassemble(uint8_t *Code, uint32_t CodeSize, uint32_t MaxInst, uint64_t StartingPC, uint32_t *InstructionCount) = 0; +}; + +std::unique_ptr CreateHostDisassembler(); +std::unique_ptr CreateGuestDisassembler(); + +} diff --git a/Source/Tools/Debugger/FEXImGui.cpp b/Source/Tools/Debugger/FEXImGui.cpp new file mode 100644 index 0000000000..4838d48e93 --- /dev/null +++ b/Source/Tools/Debugger/FEXImGui.cpp @@ -0,0 +1,153 @@ +#include "FEXImGui.h" +#include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include + +namespace FEXImGui { +using namespace ImGui; + +// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature. +bool ListBoxHeader(const char* label, int items_count) +{ + // Size default to hold ~7.25 items. + // We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar. + // We don't add this extra bit if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + const ImGuiStyle& style = GetStyle(); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + + auto Window = GetCurrentWindowRead(); + ImVec2 Base = Window->Rect().Min; + ImVec2 Size = Window->Rect().Max; + ImVec2 Height = Size - Base; + size.y = Height.y - style.FramePadding.y * 2.0f; + + return ImGui::ListBoxHeader(label, size); +} + +bool ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count) +{ + if (!ListBoxHeader(label, items_count)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + ImGuiContext& g = *GImGui; + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + ListBoxFooter(); + if (value_changed) + MarkItemEdited(g.CurrentWindow->DC.LastItemId); + + return value_changed; +} + +bool CustomIRViewer(char const *buf, size_t buf_size, std::vector *lines) { + ImGuiContext& g = *GImGui; + const float inner_spacing = g.Style.ItemInnerSpacing.x; + ImGui::Columns(2); + ImGui::SetColumnWidth(0, 100.0f + inner_spacing * 2.0f); + static float TextOffset = 0.0f; + if (ImGui::BeginChildFrame(GetID(""), ImVec2(100, -1))) { + const std::vector LineColors = { + ImVec4(1, 0, 0, 1), + ImVec4(1, 1, 0, 1), + ImVec4(0, 0, 0, 1), + ImVec4(0, 0, 1, 1), + ImVec4(1, 0, 1, 1), + ImVec4(0, 1, 1, 1), + }; + + auto Window = GetCurrentWindowRead(); + auto DrawList = GetWindowDrawList(); + + auto DrawIRLine = [&](size_t Index, float From, float To, ImVec4 Color) { + ImVec2 Size = Window->Rect().Max; + + float LineWidth = 3.0; + float LineSpacing = LineWidth * 5; + + // Zero indexing, add one + Index = Index % LineColors.size(); + Index++; + From -= TextOffset; + To -= TextOffset; + + ImVec2 LeftFrom = ImVec2(Size.x - LineSpacing * static_cast(Index), From); + ImVec2 RightFrom = ImVec2(Size.x, From); + ImVec2 LeftTo = ImVec2(Size.x - LineSpacing * static_cast(Index), To); + ImVec2 RightTo = ImVec2(Size.x, To); + + // We want to draw a three lines + // One horizontal one from the "FROM" offset + // One vertical one down the middle. Offset by Index + // Another horizontol to the "TO" offset + // + // Additionally we want a triangle pointing to the TO location + + // Draw the from line + DrawList->AddLine(LeftFrom, RightFrom, GetColorU32(Color), LineWidth); + + // Draw the to line + DrawList->AddLine(LeftTo, RightTo, GetColorU32(Color), LineWidth); + + // Draw a small filled rectangle at the target + DrawList->AddTriangleFilled(ImVec2(RightTo.x - LineWidth * 2, RightTo.y - LineWidth), ImVec2(RightTo.x - LineWidth * 2, RightTo.y + LineWidth), RightTo, GetColorU32(Color)); + + // Draw the vertical line between the two + DrawList->AddLine(LeftFrom, LeftTo, GetColorU32(Color), LineWidth); + }; + + ImVec2 Base = Window->Rect().Min; + ImVec2 Size = Window->Rect().Max; + DrawList->AddRectFilled(Base, Size, GetColorU32(ImVec4(0.5, 0.5, 0.5, 1.0))); + + float FontSize = Window->CalcFontSize(); + + for (size_t i = 0; i < lines->size(); ++i) { + float BaseOffset = Base.y + inner_spacing + FontSize / 2.0f; + DrawIRLine(i, BaseOffset + static_cast(lines->at(i).From) * FontSize, BaseOffset + static_cast(lines->at(i).To) * FontSize, LineColors[i % LineColors.size()]); + } + } + ImGui::EndChildFrame(); + + ImGui::NextColumn(); + ImGui::InputTextMultiline("##IR", const_cast(buf), buf_size, ImVec2(-1, -1), ImGuiInputTextFlags_ReadOnly); + + // This is a bit filthy + // Pulls the window and if the child is available then we can pull its child and read the scroll amount + // This lets the IR CFG lines match up with textbox, albiet a frame behind so it gets a bit of a wiggle + auto win = GetCurrentWindow(); + if (win->DC.ChildWindows.size() == 2) { + win = win->DC.ChildWindows[win->DC.ChildWindows.size() - 1]; + TextOffset = win->Scroll.y; + } + + return false; +} + + +} diff --git a/Source/Tools/Debugger/FEXImGui.h b/Source/Tools/Debugger/FEXImGui.h new file mode 100644 index 0000000000..726c420d15 --- /dev/null +++ b/Source/Tools/Debugger/FEXImGui.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include + +namespace FEXImGui { + +// Listbox that fills the child window +bool ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count); + +struct IRLines { + size_t From; + size_t To; +}; + +bool CustomIRViewer(char const *buf, size_t buf_size, std::vector *lines); + +} diff --git a/Source/Tools/Debugger/GLUtils.cpp b/Source/Tools/Debugger/GLUtils.cpp new file mode 100644 index 0000000000..5ceaadb4b1 --- /dev/null +++ b/Source/Tools/Debugger/GLUtils.cpp @@ -0,0 +1,5 @@ +#include "GLUtils.h" +#include + +namespace GLUtils { +} diff --git a/Source/Tools/Debugger/GLUtils.h b/Source/Tools/Debugger/GLUtils.h new file mode 100644 index 0000000000..b32b1b7129 --- /dev/null +++ b/Source/Tools/Debugger/GLUtils.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +namespace GLUtils { +} diff --git a/Source/Tools/Debugger/IMGui.cpp b/Source/Tools/Debugger/IMGui.cpp new file mode 100644 index 0000000000..12a5794cf9 --- /dev/null +++ b/Source/Tools/Debugger/IMGui.cpp @@ -0,0 +1,1172 @@ +#include "DebuggerState.h" +#include "Disassembler.h" +#include "FEXImGui.h" +#include "IMGui.h" +#include "LogManager.h" +#include "IRLexer.h" +#include "Common/Config.h" +#include "Common/StringUtil.h" +#include "Util/DataRingBuffer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#define YES_IMGUIFILESYSTEM +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FEX::Debugger { +double g_Time = 0.0; + +constexpr size_t TmpSize = 512; +char Tmp[TmpSize]; +namespace CPUState { + bool ShowCPUState {false}; + bool ShowCPUThreads {true}; + FEXCore::Core::CPUState PreviousState; + FEXCore::Core::CPUState CurrentState; + + int CurrentThreadSelected {0}; + + bool ThreadNameGetter(void *data, int idx, const char** out_text) { + if (FEX::DebuggerState::ActiveCore()) { + snprintf(Tmp, TmpSize, "Thread %d", idx); + *out_text = Tmp; + return true; + } + return false; + } + + void MenuItems() { + ImGui::MenuItem("CPU State", nullptr, &ShowCPUState); + ImGui::MenuItem("CPU Threads", nullptr, &ShowCPUThreads); + + if (ImGui::BeginMenu("Backend")) { + if (ImGui::MenuItem("IR Interpreter", nullptr, FEX::DebuggerState::GetCoreType() == FEXCore::Config::ConfigCore::CONFIG_INTERPRETER, !FEX::DebuggerState::ActiveCore())) + FEX::DebuggerState::SetCoreType(FEXCore::Config::ConfigCore::CONFIG_INTERPRETER); + if (ImGui::MenuItem("IR JIT", nullptr, FEX::DebuggerState::GetCoreType() == FEXCore::Config::ConfigCore::CONFIG_IRJIT, !FEX::DebuggerState::ActiveCore())) + FEX::DebuggerState::SetCoreType(FEXCore::Config::ConfigCore::CONFIG_IRJIT); + if (ImGui::MenuItem("LLVM", nullptr, FEX::DebuggerState::GetCoreType() == FEXCore::Config::ConfigCore::CONFIG_LLVMJIT, !FEX::DebuggerState::ActiveCore())) + FEX::DebuggerState::SetCoreType(FEXCore::Config::ConfigCore::CONFIG_LLVMJIT); + ImGui::EndMenu(); + } + + ImGui::Separator(); + if (ImGui::MenuItem("Single Step", nullptr, FEX::DebuggerState::GetRunningMode() == 1, !FEX::DebuggerState::IsCoreRunning())) + FEX::DebuggerState::SetRunningMode(1); + + if (ImGui::MenuItem("Regular Running", nullptr, FEX::DebuggerState::GetRunningMode() == 0, !FEX::DebuggerState::IsCoreRunning())) + FEX::DebuggerState::SetRunningMode(0); + } + + void Windows() { + if (ImGui::Begin("#CPU State", &ShowCPUState)) { + auto &State = CurrentState; + bool PushedCol = false; + #define DiffCol(X) do { if (PreviousState.X != State.X) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.0, 0.0, 1.0)); PushedCol = true;} } while (0) + #define DiffPop(X) do { if (PushedCol) { ImGui::PopStyleColor(); PushedCol = false; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Prev: 0x%lx", PreviousState.X);} } while (0) + + DiffCol(rip); + ImGui::Text("RIP: 0x%lx", State.rip); + DiffPop(rip); + + if (ImGui::CollapsingHeader("GPRs")) { + const std::vector GPRNames = { + "RAX", + "RBX", + "RCX", + "RDX", + "RSI", + "RDI", + "RBP", + "RSP", + " R8", + " R9", + "R10", + "R11", + "R12", + "R13", + "R14", + "R15", + }; + + for (unsigned i = 0; i < 16; ++i) { + DiffCol(gregs[i]); + ImGui::Text("%s: 0x%016lx", GPRNames[i], State.gregs[i]); + DiffPop(gregs[i]); + } + } + if (ImGui::CollapsingHeader("XMMs")) { + for (unsigned i = 0; i < 16; ++i) { + DiffCol(xmm[i][0]); + ImGui::Text("%2d: 0x%016lx - 0x%016lx", i, State.xmm[i][0], State.xmm[i][1]); + DiffPop(xmm[i][0]); + } + } + if (ImGui::CollapsingHeader("FLAGs")) { + // DiffCol(rflags); + // ImGui::Text("EFLAG: 0x%x", static_cast(State.rflags)); + // DiffPop(rflags); + // #define PRINT_FLAG(X, Y) ImGui::Text(#X ": %d " #Y ": %d", !!(State.rflags & FEXCore::X86State::RFLAG_ ## X ## _LOC), !!(State.rflags & FEXCore::X86State::RFLAG_ ## Y ## _LOC)) + // PRINT_FLAG(CF, PF); + // PRINT_FLAG(AF, ZF); + // PRINT_FLAG(SF, OF); + // ImGui::Separator(); + // PRINT_FLAG(TF, IF); + // PRINT_FLAG(DF, IOPL); + // PRINT_FLAG(NT, RF); + // PRINT_FLAG(VM, AC); + // PRINT_FLAG(VIF, VIP); + // ImGui::Text("ID: %d", !!(State.rflags & FEXCore::X86State::RFLAG_ID_LOC)); + // #undef PRINT_FLAG + } + + ImGui::Separator(); + DiffCol(fs); + ImGui::Text("FS: 0x%lx", State.fs); + DiffPop(fs); + DiffCol(gs); + ImGui::Text("GS: 0x%lx", State.gs); + DiffPop(gs); + #undef DiffCol + #undef DiffPop + + FEX::DebuggerState::SetHasNewState(false); + } + ImGui::End(); + + if (ImGui::Begin("#CPU Threads", &ShowCPUThreads)) { + size_t ThreadCount = 0; + if (FEX::DebuggerState::ActiveCore()) { + ThreadCount = FEXCore::Context::Debug::GetThreadCount(FEX::DebuggerState::GetContext()); + } + ImGui::Text("Threads: %ld", ThreadCount); + ImGui::ListBox("##Threads", &CurrentThreadSelected, ThreadNameGetter, nullptr, ThreadCount); + } + ImGui::End(); + } + + void Status() { + ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetFrameHeight()); + + bool PushedStyle = false; + if (!FEX::DebuggerState::IsCoreRunning() || + FEX::DebuggerState::GetCoreCurrentRunningMode() == 0) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0, 0.0, 0.0, 1.0)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.0, 1.0, 0.0, 1.0)); + PushedStyle = true; + } + else if (FEX::DebuggerState::IsCoreRunning() && + FEX::DebuggerState::GetCoreCurrentRunningMode() == 1) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0, 1.0, 0.0, 1.0)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.0, 1.0, 0.0, 1.0)); + PushedStyle = true; + } + + ImGui::RadioButton("", FEX::DebuggerState::IsCoreRunning()); + + if (PushedStyle) { + ImGui::PopStyleColor(2); + } + } + + void NewState() { + PreviousState = CurrentState; + CurrentState = FEX::DebuggerState::GetCPUState(); + } +} + +// CPU stats window +namespace CPUStats { + bool ShowCPUStats {true}; + FEX::Debugger::Util::DataRingBuffer InstExecuted(60 * 10); + FEX::Debugger::Util::DataRingBuffer BlocksCompiled(60 * 10); + auto LastTime = std::chrono::high_resolution_clock::now(); + + void Window() { + auto Now = std::chrono::high_resolution_clock::now(); + if ((Now - LastTime) >= std::chrono::seconds(1)) { + LastTime = Now; + if (FEX::DebuggerState::ActiveCore()) { + auto RuntimeStats = FEXCore::Context::Debug::GetRuntimeStatsForThread(FEX::DebuggerState::GetContext(), CPUState::CurrentThreadSelected); + InstExecuted.push_back(RuntimeStats->InstructionsExecuted); + BlocksCompiled.push_back(RuntimeStats->BlocksCompiled); + RuntimeStats->InstructionsExecuted = 0; + RuntimeStats->BlocksCompiled = 0; + } + } + + if (ImGui::Begin("#CPU Stats", &ShowCPUStats)) { + if (!InstExecuted.empty()) { + ImGui::PlotHistogram("MIPS", InstExecuted(), InstExecuted.size(), 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, 50)); + ImGui::SameLine(); + ImGui::Text("%f", InstExecuted.back()); + } + + if (!BlocksCompiled.empty()) { + ImGui::PlotHistogram("Blocks Compiled", BlocksCompiled(), BlocksCompiled.size(), 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, 50)); + ImGui::SameLine(); + ImGui::Text("%f", BlocksCompiled.back()); + } + + } + ImGui::End(); + } +} + +namespace MemoryViewer { + std::vector MappedRegions; +} + +// Logging Windows +namespace Logging { + bool ShowLogOutput = true; + bool ShowLogstdout = true; + bool ShowLogstderr = true; + + bool ScrollLogWindow = false; + std::vector LogData; + std::vector stdoutData; + std::vector stderrData; + constexpr size_t MAX_LOG_SIZE = 10000; + std::string IRData; + + void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + break; + case LogMan::STDOUT: + CharLevel = "stdout"; + break; + case LogMan::STDERR: + CharLevel = "stderr"; + break; + + default: + CharLevel = "???"; + break; + } + + auto AddTo = [](auto &Output, std::string &Log) { + Output.emplace_back(Log); + if (Output.size() > MAX_LOG_SIZE) { + Output.erase(Output.begin(), Output.begin() + (Output.size() - MAX_LOG_SIZE)); + } + }; + + std::string Log; + Log += "["; + Log += CharLevel; + Log += "] "; + Log += Message; + FEX::StringUtil::rtrim(Log); + AddTo(LogData, Log); + if (Level == LogMan::STDOUT) + AddTo(stdoutData, Log); + if (Level == LogMan::STDERR) + AddTo(stderrData, Log); + + printf("[%s] %s\n", CharLevel, Message); + ScrollLogWindow = true; + } + + void AssertHandler(char const *Message) { + std::string Log; + Log += "[ASSERT] "; + Log += Message; + FEX::StringUtil::rtrim(Log); + LogData.emplace_back(Log); + + if (LogData.size() > MAX_LOG_SIZE) { + LogData.erase(LogData.begin(), LogData.begin() + (LogData.size() - MAX_LOG_SIZE)); + } + printf("[ASSERT] %s\n", Message); + ScrollLogWindow = true; + } + + void LogWindow(bool *LogBool, const char *Name, std::vector const &Log) { + if (LogBool) { + if (ImGui::Begin(Name, LogBool)) { + ImGui::BeginChild("LogConsole"); + for (auto &Text : Log) { + bool pop_color = false; + if (strstr(Text.c_str(), "ERROR")) { + pop_color = true; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.5f, 1.0f)); + } + if (strstr(Text.c_str(), "ASSERT")) { + pop_color = true; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + } + if (strstr(Text.c_str(), "DEBUG")) { + pop_color = true; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); + } + + ImGui::TextUnformatted(Text.c_str()); + + if (pop_color) { + ImGui::PopStyleColor(); + } + } + if (ScrollLogWindow) { + ImGui::SetScrollHereY(1.0f); + ScrollLogWindow = false; + } + ImGui::EndChild(); + } + ImGui::End(); + } + } + + void Windows() { + LogWindow(&ShowLogOutput, "#Log", LogData); + LogWindow(&ShowLogstderr, "#Log stderr", stderrData); + LogWindow(&ShowLogstdout, "#Log stdout", stdoutData); + } + + void Init() { + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + } + + void MenuItems() { + ImGui::MenuItem("Log", nullptr, &ShowLogOutput); + ImGui::MenuItem("LogO", nullptr, &ShowLogstdout); + ImGui::MenuItem("LogE", nullptr, &ShowLogstderr); + } +} + +namespace Disasm { + bool ShowCPUHostDisasm = true; + bool ShowCPUGuestDisasm = true; + + uint64_t CurrentDisasmRIP {}; + std::string HostDisasmString; + std::string GuestDisasmString; + std::unique_ptr HostDisassembler; + std::unique_ptr GuestDisassembler; + + void MenuItems() { + ImGui::MenuItem("CPU Guest Disasm", nullptr, &ShowCPUGuestDisasm); + ImGui::MenuItem("CPU Host Disasm", nullptr, &ShowCPUHostDisasm); + } + + void Windows() { + if (ImGui::Begin("#Guest Disasm", &ShowCPUGuestDisasm)) { + ImGui::InputTextMultiline("", const_cast(GuestDisasmString.c_str()), GuestDisasmString.size(), ImVec2(-1, -1), ImGuiInputTextFlags_ReadOnly); + } + ImGui::End(); + + if (ImGui::Begin("#Host Disasm", &ShowCPUHostDisasm)) { + ImGui::InputTextMultiline("", const_cast(HostDisasmString.c_str()), HostDisasmString.size(), ImVec2(-1, -1), ImGuiInputTextFlags_ReadOnly); + } + ImGui::End(); + } + + uint8_t *GetPointerFromRegions(uint64_t PC) { + for (auto &Region : MemoryViewer::MappedRegions) { + if (Region.contains(PC)) { + uint64_t Offset = PC - Region.Offset; + return reinterpret_cast(reinterpret_cast(Region.Ptr) + Offset); + } + } + + return nullptr; + } + + void DisasmGuest(uint64_t PC, int Size) { + CurrentDisasmRIP = PC; + uint8_t *GuestCode = GetPointerFromRegions(PC); + uint32_t InstCount{}; + GuestDisasmString = GuestDisassembler->Disassemble(GuestCode, Size, 255, PC, &InstCount); + + bool DebugBlockExists; + bool HostCodeBlockExists; + uint8_t *HostCodePtr; + FEXCore::Core::DebugData DebugData; + HostCodeBlockExists = FEXCore::Context::Debug::FindHostCodeForRIP(FEX::DebuggerState::GetContext(), PC, &HostCodePtr); + DebugBlockExists = FEXCore::Context::Debug::GetDebugDataForRIP(FEX::DebuggerState::GetContext(), PC, &DebugData); + + if (!DebugBlockExists || !HostCodeBlockExists || DebugData.HostCodeSize == 0) { + // Didn't have a block backing this PC. + HostDisasmString = ""; + } + else { + HostDisasmString = HostDisassembler->Disassemble(static_cast(HostCodePtr), DebugData.HostCodeSize, -1U, reinterpret_cast(HostCodePtr), &InstCount); + } + } + + void Disasm(uint64_t PC) { + CurrentDisasmRIP = PC; + uint8_t *GuestCode = GetPointerFromRegions(PC); + uint32_t InstCount{}; + + bool DebugBlockExists; + bool HostCodeBlockExists; + uint8_t *HostCodePtr; + FEXCore::Core::DebugData DebugData; + HostCodeBlockExists = FEXCore::Context::Debug::FindHostCodeForRIP(FEX::DebuggerState::GetContext(), PC, &HostCodePtr); + DebugBlockExists = FEXCore::Context::Debug::GetDebugDataForRIP(FEX::DebuggerState::GetContext(), PC, &DebugData); + + GuestDisasmString = GuestDisassembler->Disassemble(GuestCode, DebugData.GuestCodeSize, DebugData.GuestInstructionCount, PC, &InstCount); + + if (!DebugBlockExists || !HostCodeBlockExists || DebugData.HostCodeSize == 0) { + // Didn't have a block backing this PC. + HostDisasmString = ""; + } + else { + HostDisasmString = HostDisassembler->Disassemble(static_cast(HostCodePtr), DebugData.HostCodeSize, -1U, reinterpret_cast(HostCodePtr), &InstCount); + } + } + + void Init() { + HostDisassembler = FEX::Debugger::CreateHostDisassembler(); + GuestDisassembler = FEX::Debugger::CreateGuestDisassembler(); + } +} + +namespace IR { + bool ShowCPUIR = true; + bool ShowCPUIRList = true; + + struct IRDebugData { + FEXCore::Core::DebugData *Debug; + uint64_t RIP; + std::string RIPString; + std::string GuestCodeSize; + std::string GuestInstructionCount; + }; + + std::vector IRListTexts; + int CPUIRListCurrentItem {}; + + struct IRFileHeader { + uint64_t RIP; + size_t IRSize; + }; + + bool HadSelectedSaveIR{}; + ImGuiFs::Dialog Dialog; + FEX::Debugger::IR::Lexer Lexer; + std::vector IRLines; + + //void CalculateCFGLines(FEXCore::IR::IntrusiveIRList *IR) { + // IRLines.clear(); + // size_t Line {}; + // size_t i {}; + // size_t Size = IR->GetOffset(); + + // // From->To: Offset->AlignmentmentType + // std::unordered_map MapFromTo{}; + + // // To->From: AlignmentType->Offset + // std::unordered_map> MapToFrom{}; + + // // Offset->Line + // std::unordered_map OffsetToLine{}; + + // while (i != Size) { + // auto IROp = IR->GetOp(i); + // auto OpSize = FEXCore::IR::GetSize(IROp->Op); + + // switch (IROp->Op) { + // case FEXCore::IR::OP_JUMP: { + // auto Op = IROp->C(); + // MapFromTo[i] = Op->Location.Off; + // MapToFrom[Op->Location.Off].emplace_back(i); + // break; + // } + // case FEXCore::IR::OP_CONDJUMP: { + // auto Op = IROp->C(); + // MapFromTo[i] = Op->Location.Off; + // MapToFrom[Op->Location.Off].emplace_back(i); + // break; + // } + + // default: break; + // } + + // OffsetToLine[i] = Line; + // i += OpSize; + // ++Line; + // } + + // for (auto From : MapFromTo) { + // auto To = MapToFrom.find(From.second); + // auto FromLine = OffsetToLine[From.first]; + // auto ToLine = OffsetToLine[To->first]; + // IRLines.emplace_back(FEXImGui::IRLines{FromLine, ToLine}); + // } + //} + + bool IRListGetter(void *data, int idx, const char** out_text) { + int Type = reinterpret_cast(data); + switch (Type) { + case 1: + *out_text = IRListTexts[idx].RIPString.c_str(); + break; + case 2: + *out_text = IRListTexts[idx].GuestCodeSize.c_str(); + break; + case 3: + *out_text = IRListTexts[idx].GuestInstructionCount.c_str(); + break; + } + return true; + } + + void MenuItems() { + ImGui::MenuItem("IR Viewer", nullptr, &ShowCPUIR); + ImGui::MenuItem("IR List", nullptr, &ShowCPUIRList); + } + + void Windows() { + auto SortByRIP = [](IRDebugData const &a, IRDebugData const &b) -> bool { + return a.RIP < b.RIP; + }; + + auto SortByCodeSize = [](IRDebugData const &a, IRDebugData const &b) -> bool { + return a.Debug->GuestCodeSize < b.Debug->GuestCodeSize; + }; + auto SortByInstCount = [](IRDebugData const &a, IRDebugData const &b) -> bool { + return a.Debug->GuestInstructionCount < b.Debug->GuestInstructionCount; + }; + + if (ImGui::Begin("#CPU IR Entries", &ShowCPUIRList)) { + if (ImGui::Button("Sort RIP")) { + std::sort(IRListTexts.begin(), IRListTexts.end(), SortByRIP); + } + ImGui::SameLine(); + if (ImGui::Button("Sort CodeSize")) { + std::sort(IRListTexts.begin(), IRListTexts.end(), SortByCodeSize); + } + + ImGui::SameLine(); + if (ImGui::Button("Sort Inst Count")) { + std::sort(IRListTexts.begin(), IRListTexts.end(), SortByInstCount); + } + + ImGui::Columns(3); + ImGui::Text("RIPS"); + ImGui::NextColumn(); + ImGui::Text("CodeSize"); + ImGui::NextColumn(); + ImGui::Text("InstructionCount"); + ImGui::NextColumn(); + + ImGui::PushItemWidth(-1.0); + + bool UpdateSelection = false; + UpdateSelection |= ImGui::ListBox("##RIP", &CPUIRListCurrentItem, IRListGetter, reinterpret_cast(1), IRListTexts.size()); + ImGui::PopItemWidth(); + + ImGui::NextColumn(); + ImGui::PushItemWidth(-1.0); + UpdateSelection |= ImGui::ListBox("##CodeSize", &CPUIRListCurrentItem, IRListGetter, reinterpret_cast(2), IRListTexts.size()); + ImGui::PopItemWidth(); + + ImGui::NextColumn(); + ImGui::PushItemWidth(-1.0); + UpdateSelection |= ImGui::ListBox("##InstCount", &CPUIRListCurrentItem, IRListGetter, reinterpret_cast(3), IRListTexts.size()); + ImGui::PopItemWidth(); + + if (UpdateSelection) { + uint64_t PC = IRListTexts[CPUIRListCurrentItem].RIP; + std::stringstream out; + FEX::DebuggerState::GetIR(&out, PC); + Disasm::Disasm(PC); + Logging::IRData = out.str(); + + // FEXCore::IR::IntrusiveIRList *ir; + // bool HadIR = FEXCore::Context::Debug::FindIRForRIP(FEX::DebuggerState::GetContext(), Disasm::CurrentDisasmRIP, &ir); + // if (HadIR) { + // IR::CalculateCFGLines(ir); + // } + } + } + + ImGui::End(); + + if (ImGui::Begin("#IR Viewer", &ShowCPUIR)) { + HadSelectedSaveIR = ImGui::Button("Save IR"); + FEXImGui::CustomIRViewer(Logging::IRData.c_str(), Logging::IRData.size(), &IRLines); + } + ImGui::End(); + + char const *InitialPath; + char const *File; + + InitialPath = Dialog.saveFileDialog(HadSelectedSaveIR, "./"); + File = Dialog.getChosenPath(); + if (strlen(InitialPath) > 0 && strlen(File) > 0) { + // FEXCore::IR::IntrusiveIRList *ir; + // bool HadIR = FEXCore::Context::Debug::FindIRForRIP(FEX::DebuggerState::GetContext(), Disasm::CurrentDisasmRIP, &ir); + // if (HadIR) { + // IRFileHeader Header; + // Header.RIP = Disasm::CurrentDisasmRIP; + // Header.IRSize = ir->GetOffset(); + // std::fstream IRFile; + // IRFile.open(File, std::fstream::out | std::fstream::binary); + // LogMan::Throw::A(IRFile.is_open(), "Failed to open file"); + + // IRFile.write(reinterpret_cast(&Header), sizeof(Header)); + // IRFile.write(ir->GetOpAs(0), Header.IRSize); + // IRFile.close(); + // } + } + } + + void LoadIR(char const *Filename) { + IRFileHeader Header; + std::fstream IRFile; + IRFile.open(Filename, std::fstream::in | std::fstream::binary); + LogMan::Throw::A(IRFile.is_open(), "Failed to open file"); + + IRFile.read(reinterpret_cast(&Header), sizeof(Header)); + + // FEXCore::IR::IntrusiveIRList IR(Header.IRSize); + // char *Ptr = IR.SetupForLoad(Header.IRSize); + // IRFile.read(Ptr, Header.IRSize); + // IRFile.close(); + // FEXCore::Context::Debug::SetIRForRIP(FEX::DebuggerState::GetContext(), Header.RIP, &IR); + // FEX::DebuggerState::SetHasNewState(); + // FEX::DebuggerState::CallNewState(); + } + + void NewState() { + CPUIRListCurrentItem = 0; + IRListTexts.clear(); + + // CPU core could have been removed + if (!FEX::DebuggerState::ActiveCore()) { + return; + } + + FEXCore::Core::ThreadState *State = FEXCore::Context::Debug::GetThreadState(FEX::DebuggerState::GetContext()); + FEXCore::Core::InternalThreadState *TS = reinterpret_cast(State); + + auto &IRList = TS->IRLists; + auto &DebugData = TS->DebugData; + + for (auto &IR : IRList) { + std::ostringstream out; + out << "0x" << std::hex << IR.first; + auto Data = DebugData.find(IR.first); + IRDebugData DebugData; + DebugData.Debug = &Data->second; + DebugData.RIP = IR.first; + DebugData.RIPString = out.str(); + DebugData.GuestCodeSize = std::to_string(Data->second.GuestCodeSize); + DebugData.GuestInstructionCount = std::to_string(Data->second.GuestInstructionCount); + IRListTexts.emplace_back(DebugData); + } + } +} + +// History handling +namespace History { + enum HistoryType { + TYPE_ELF, + TYPE_TEST_HARNESS, + TYPE_IR, + TYPE_X86, + }; + std::list> HistoryItems; + + void Save() { + // Save History + char Buffer[512]; + char *Dest; + Dest = json_objOpen(Buffer, nullptr); + Dest = json_objOpen(Dest, "History"); + for (auto &it : HistoryItems) { + Dest = json_int(Dest, it.second.c_str(), it.first); + } + Dest = json_objClose(Dest); + Dest = json_objClose(Dest); + json_end(Dest); + + std::fstream HistoryFile; + HistoryFile.open("History.json", std::fstream::out); + LogMan::Throw::A(HistoryFile.is_open(), "Failed to open file"); + + HistoryFile.write(Buffer, strlen(Buffer)); + HistoryFile.close(); + } + + void Add(HistoryType Type, const char *Filename) { + for (auto it = HistoryItems.begin(); it != HistoryItems.end(); ++it) { + if (it->first == Type && it->second == Filename) { + if (it != HistoryItems.begin()) { + HistoryItems.splice(HistoryItems.begin(), HistoryItems, it, std::next(it)); + Save(); + } + return; + } + } + + HistoryItems.emplace_front(std::make_pair(Type, Filename)); + if (HistoryItems.size() > 5) { + auto it = HistoryItems.begin(); + std::advance(it, 5); + HistoryItems.erase(it, HistoryItems.end()); + } + Save(); + } + + void MenuItems() { + for (auto &item : HistoryItems) { + std::string Item{}; + switch (item.first) { + case TYPE_ELF: Item = "ELF: "; break; + case TYPE_TEST_HARNESS: Item = "Test: "; break; + case TYPE_IR: Item = "IR: "; break; + case TYPE_X86: Item = "X86: "; break; + } + auto Last = item.second.find_last_of('/') + 1; + std::string Filename = item.second.substr(Last); + Item += Filename; + if (ImGui::MenuItem(Item.c_str(), nullptr, nullptr, !FEX::DebuggerState::ActiveCore() || item.first == TYPE_IR)) { + if (item.first == TYPE_IR) { + IR::LoadIR(item.second.c_str()); + } + else { + FEX::DebuggerState::Create(item.second.c_str(), item.first == TYPE_ELF); + } + History::Add(item.first, item.second.c_str()); + } + } + } + + void Init() { + // Load History + std::fstream HistoryFile; + std::vector Data; + HistoryFile.open("History.json", std::fstream::in); + if (HistoryFile.is_open()) { + HistoryFile.seekg(0, std::fstream::end); + size_t FileSize = HistoryFile.tellg(); + HistoryFile.seekg(0, std::fstream::beg); + + Data.resize(FileSize); + HistoryFile.read(&Data.at(0), FileSize); + HistoryFile.close(); + + json_t elem[32]; + json_t const* json = json_create(&Data.at(0), elem, sizeof(elem) / sizeof(json_t)); + + json_t const* HistoryList = json_getProperty(json, "History"); + for (json_t const* HistoryItem = json_getChild(HistoryList); + HistoryItem != nullptr; + HistoryItem = json_getSibling(HistoryItem)) { + const char* HistoryName = json_getName(HistoryItem); + const char* HistoryType = json_getValue(HistoryItem); + History::Add(static_cast(atol(HistoryType)), HistoryName); + } + } + } +} + +// Config +namespace Config { + bool ShowConfig = true; + struct Configs { + FEX::Config::Value ConfigMaxInst{"MaxInst", 255}; + FEX::Config::Value ConfigCore{"Core", 0}; + FEX::Config::Value ConfigRunningMode{"RunningMode", 0}; + FEX::Config::Value ConfigMultiblock{"Multiblock", false}; + }; + + std::unique_ptr Config; + int NewInstMax {}; + bool Multiblock; + + void Window() { + if (ImGui::Begin("#Config", &ShowConfig)) { + if (ImGui::SliderInt("Max Inst", &NewInstMax, -1, 255)) { + Config->ConfigMaxInst.Set(NewInstMax); + } + + if (ImGui::Checkbox("Multiblock", &Multiblock)) { + Config->ConfigMultiblock.Set(Multiblock); + } + } + ImGui::End(); + } + + void Init() { + Config = std::make_unique(); + NewInstMax = Config->ConfigMaxInst(); + Multiblock = Config->ConfigMultiblock(); + FEX::DebuggerState::SetCoreType(static_cast(Config::Config->ConfigCore())); + FEX::DebuggerState::SetRunningMode(Config::Config->ConfigRunningMode()); + } + + void Shutdown() { + Config::Config->ConfigCore.Set(FEX::DebuggerState::GetCoreType()); + Config::Config->ConfigRunningMode.Set(FEX::DebuggerState::GetRunningMode()); + } +} + +// Memory viewer window +namespace MemoryViewer { + bool ShowMemoryWindow = false; + int MappedRegionsCurrentItem {}; + int MappedRegionsMemoryCurrentItem {}; + int CodeSize {128}; + uint64_t CurrentAddress {}; + char CurrentAddressString[32]{}; + bool MappedRegionGetter(void *data, int idx, const char** out_text) { + auto &Region = MappedRegions[idx]; + static char Buf[512]; + snprintf(Buf, 512, "[0x%lx, 0x%lx) - 0x%lx", Region.Offset, Region.Offset + Region.Size, Region.Size); + *out_text = Buf; + return true; + } + + bool MappedRegionMemoryGetter(void *data, int idx, const char** out_text) { + if (MappedRegions.empty()) + return false; + + auto &Region = MappedRegions[MappedRegionsCurrentItem]; + static char Buf[512]{}; + char BufChars[17]{}; + uint8_t *Ptr = static_cast(Region.Ptr); + Ptr += idx * 16; + + for (int i = 0; i < 16; i++) { + if (isprint(Ptr[i])) + BufChars[i] = Ptr[i]; + else + BufChars[i] = '.'; + } + + snprintf(Buf, 512, + "%4lX > %02x %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\t%s", + Region.Offset + idx * 16, + Ptr[0], Ptr[1], Ptr[2], Ptr[3], Ptr[4], Ptr[5], Ptr[6], Ptr[7], + Ptr[8], Ptr[9], Ptr[10], Ptr[11], Ptr[12], Ptr[13], Ptr[14], Ptr[15], + BufChars + ); + *out_text = Buf; + return true; + } + + void Window() { + auto DisplayAddress = [&](uint64_t RIP) { + MappedRegionsCurrentItem = 0; + // First we need to find the RIP in the mapped regions + // Skip first entry because it is the full 64GB of vmem + for (size_t i = 1; i < MappedRegions.size(); ++i) { + if (MappedRegions[i].contains(RIP)) { + MappedRegionsCurrentItem = i; + break; + } + } + // Now we need to set our hex view listbox to the correct line + MappedRegionsMemoryCurrentItem = static_cast(RIP - MappedRegions[MappedRegionsCurrentItem].Offset) / 16; + + // Now set the Current Address textbox and string to the correct address + LogMan::Msg::D("RIP is 0x%lx", RIP); + CurrentAddress = RIP; + std::ostringstream out{}; + out << "0x" << std::hex << CurrentAddress; + strncpy(CurrentAddressString, out.str().c_str(), 32); + }; + + if (ImGui::Begin("#Memory Viewer", &ShowMemoryWindow)) { + if (FEX::DebuggerState::ActiveCore() && !FEX::DebuggerState::IsCoreRunning()) { + FEXCore::Context::Debug::GetMemoryRegions(FEX::DebuggerState::GetContext(), &MappedRegions); + } + + if (!MappedRegions.empty()) { + ImGui::PushItemWidth(-1.0); + if (ImGui::ListBox("Mapped Regions", &MappedRegionsCurrentItem, MappedRegionGetter, nullptr, MappedRegions.size())) { + } + ImGui::PopItemWidth(); + + if (ImGui::InputText("RIP:", CurrentAddressString, 32, ImGuiInputTextFlags_EnterReturnsTrue)) { + DisplayAddress(std::stoull(CurrentAddressString, nullptr, 0)); + } + + ImGui::DragInt("Size", &CodeSize, 1.0f, 1, 255); + if (ImGui::Button("Disasm")) { + CurrentAddress = std::stoull(CurrentAddressString, nullptr, 0); + Disasm::DisasmGuest(CurrentAddress, CodeSize); + } + if (FEX::DebuggerState::IsCoreRunning()) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + if (ImGui::Button("Compile RIP")) { + FEXCore::Context::Debug::CompileRIP(FEX::DebuggerState::GetContext(), CurrentAddress); + + std::stringstream out; + FEX::DebuggerState::GetIR(&out, CurrentAddress); + Disasm::Disasm(CurrentAddress); + Logging::IRData = out.str(); + + // FEXCore::IR::IntrusiveIRList *ir; + // bool HadIR = FEXCore::Context::Debug::FindIRForRIP(FEX::DebuggerState::GetContext(), CurrentAddress, &ir); + // if (HadIR) { + // IR::CalculateCFGLines(ir); + // } + } + if (FEX::DebuggerState::IsCoreRunning()) { + ImGui::PopStyleVar(); + ImGui::PopItemFlag(); + } + + if (ImGui::Button("Go to current RIP")) { + if (FEX::DebuggerState::ActiveCore()) { + DisplayAddress(CPUState::CurrentState.rip); + } + } + + if (ImGui::BeginChildFrame(ImGui::GetID("Hex"), ImVec2(-1, -1))) { + ImGui::PushItemWidth(-1.0); + + if (FEXImGui::ListBox("##Memory Hex", + &MappedRegionsMemoryCurrentItem, + MappedRegionMemoryGetter, + nullptr, + static_cast(MappedRegions[MappedRegionsCurrentItem].Size) / 16)) { + CurrentAddress = MappedRegions[MappedRegionsCurrentItem].Offset + MappedRegionsMemoryCurrentItem * 16; + std::ostringstream out{}; + out << "0x" << std::hex << CurrentAddress; + strncpy(CurrentAddressString, out.str().c_str(), 32); + } + ImGui::PopItemWidth(); + } + ImGui::EndChildFrame(); + } + + } + ImGui::End(); + } + + void Menu() { + if (ImGui::BeginMenu("Memory")) { + ImGui::MenuItem("Memory Viewer", nullptr, &ShowMemoryWindow); + ImGui::EndMenu(); + } + } +} + +namespace FileDialogs { + bool HadSelectedLaunchHarness = false; + bool HadSelectedLaunchELF = false; + bool HadSelectedIR = false; + bool HadSelectedX86 = false; + + ImGuiFs::Dialog DialogELF{}; + ImGuiFs::Dialog DialogTest{}; + ImGuiFs::Dialog DialogIR{}; + ImGuiFs::Dialog DialogX86{}; + + void MenuItems() { + if (ImGui::BeginMenu("Launch")) { + HadSelectedLaunchELF = ImGui::MenuItem("ELF", nullptr, false, !FEX::DebuggerState::ActiveCore()); + HadSelectedLaunchHarness = ImGui::MenuItem("TestHarness", nullptr, false, !FEX::DebuggerState::ActiveCore()); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Load")) { + HadSelectedIR = ImGui::MenuItem("IR", nullptr, false, FEX::DebuggerState::ActiveCore()); + HadSelectedX86 = ImGui::MenuItem("X86", nullptr, false, !FEX::DebuggerState::ActiveCore()); + ImGui::EndMenu(); + } + //ImGui::MenuItem("Connect"); + } + + void Windows() { + char const *InitialPath; + char const *File; + + InitialPath = DialogELF.chooseFileDialog(HadSelectedLaunchELF, "./"); + File = DialogELF.getChosenPath(); + if (strlen(InitialPath) > 0 && strlen(File) > 0) { + FEX::DebuggerState::Create(File, true); + History::Add(History::TYPE_ELF, File); + } + + InitialPath = DialogTest.chooseFileDialog(HadSelectedLaunchHarness, "./"); + File = DialogTest.getChosenPath(); + if (strlen(InitialPath) > 0 && strlen(File) > 0) { + FEX::DebuggerState::Create(File, false); + History::Add(History::TYPE_TEST_HARNESS, File); + } + + InitialPath = DialogIR.chooseFileDialog(HadSelectedIR, "./"); + File = DialogIR.getChosenPath(); + if (strlen(InitialPath) > 0 && strlen(File) > 0) { + IR::LoadIR(File); + History::Add(History::TYPE_IR, File); + } + + InitialPath = DialogX86.chooseFileDialog(HadSelectedX86, "./"); + File = DialogX86.getChosenPath(); + if (strlen(InitialPath) > 0 && strlen(File) > 0) { + FEX::DebuggerState::Create(File, false); + History::Add(History::TYPE_X86, File); + } + + HadSelectedLaunchHarness = false; + HadSelectedLaunchELF = false; + HadSelectedIR = false; + HadSelectedX86 = false; + } +} + +void NewStateUpdate() { + CPUState::NewState(); + IR::NewState(); +} + +void Init() { + Logging::Init(); + + FEX::DebuggerState::RegisterNewStateCallback(NewStateUpdate); + + Disasm::Init(); + Config::Init(); + History::Init(); +} + +void Shutdown() { + FEX::DebuggerState::Close(); + Config::Shutdown(); + History::Save(); +} + +void DrawDebugUI(GLContext::Context *Context) { + ImGuiIO& io = ImGui::GetIO(); + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? static_cast(current_time - g_Time) : static_cast(1.0f/60.0f); + g_Time = current_time; + + ImGui::NewFrame(); + + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, + // because it would be confusing to have two docking targets within each others. + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("DockSpace", nullptr, window_flags); + ImGui::PopStyleVar(3); + + ImGui::DockSpace(ImGui::GetID("DockSpace")); + + bool CanStep = FEX::DebuggerState::ActiveCore(); + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) { + FileDialogs::MenuItems(); + if (ImGui::MenuItem("Close", nullptr, false, FEX::DebuggerState::ActiveCore())) { + FEX::DebuggerState::Close(); + } + ImGui::Separator(); + History::MenuItems(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("CPU")) { + ImGui::MenuItem("CPU Stats", nullptr, &CPUStats::ShowCPUStats); + CPUState::MenuItems(); + ImGui::Separator(); + Disasm::MenuItems(); + ImGui::Separator(); + IR::MenuItems(); + ImGui::EndMenu(); + } + + MemoryViewer::Menu(); + + if (ImGui::BeginMenu("Debug")) { + if (ImGui::MenuItem("Step", "F11", false, CanStep)) { + FEX::DebuggerState::Step(); + } + if (ImGui::MenuItem("Pause", "Shift+F5", false, CanStep)) { + FEX::DebuggerState::Pause(); + } + if (ImGui::MenuItem("Continue", "F5", false, CanStep)) { + FEX::DebuggerState::Continue(); + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Log")) { + Logging::MenuItems(); + ImGui::EndMenu(); + } + CPUState::Status(); + + ImGui::EndMenuBar(); + } + + ImGui::End(); // End dockspace + + Disasm::Windows(); + CPUState::Windows(); + CPUStats::Window(); + Config::Window(); + IR::Windows(); + MemoryViewer::Window(); + FileDialogs::Windows(); + Logging::Windows(); + + ImGui::Render(); + if (CanStep) { + if (ImGui::IsKeyPressed(GLFW_KEY_F11)) { + FEX::DebuggerState::Step(); + } + + if (ImGui::IsKeyPressed(GLFW_KEY_F5) && io.KeyShift) { + FEX::DebuggerState::Pause(); + } + + if (ImGui::IsKeyPressed(GLFW_KEY_F5) && !io.KeyShift) { + FEX::DebuggerState::Continue(); + } + } +} + +} diff --git a/Source/Tools/Debugger/IMGui.h b/Source/Tools/Debugger/IMGui.h new file mode 100644 index 0000000000..a5d46e1225 --- /dev/null +++ b/Source/Tools/Debugger/IMGui.h @@ -0,0 +1,8 @@ +#include "Context.h" + +namespace FEX::Debugger { +void Init(); +void Shutdown(); +void DrawDebugUI(GLContext::Context *Context); + +} diff --git a/Source/Tools/Debugger/IRLexer.cpp b/Source/Tools/Debugger/IRLexer.cpp new file mode 100644 index 0000000000..a78568f31a --- /dev/null +++ b/Source/Tools/Debugger/IRLexer.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "IRLexer.h" +#include "LogManager.h" +#include "Common/StringUtil.h" + +#include + +namespace FEX::Debugger::IR { +bool Lexer::Lex(char const *IR) { +// std::istringstream iss {std::string(IR)}; +// HadError = false; +// +// [[maybe_unused]] int CurrentLine {}; +// [[maybe_unused]] int CurrentColumn {}; +// while (true) { +// char Line[256]; +// char *Str = Line; +// std::string LineStr; +// std::getline(iss, LineStr); +// FEX::StringUtil::trim(LineStr); +// strncpy(Line, LineStr.c_str(), 256); +// +// struct { +// bool HadDest; +// FEXCore::IR::AlignmentType DestLoc; +// } IRData; +// +// if (strstr(Str, "%ssa") == nullptr) { +// IRData.HadDest = true; +// sscanf(Str, "%%ssa%d =", reinterpret_cast(&IRData.DestLoc)); +// strtok(Str, " = "); +// strtok(nullptr, " = "); +// } +// +// CurrentColumn = false; +// ++CurrentLine; +// } +// // Our IR is fairly simple to lex, just spin through it +// return HadError; + return false; +} +} diff --git a/Source/Tools/Debugger/IRLexer.h b/Source/Tools/Debugger/IRLexer.h new file mode 100644 index 0000000000..54aad0e037 --- /dev/null +++ b/Source/Tools/Debugger/IRLexer.h @@ -0,0 +1,11 @@ +#pragma once + +namespace FEX::Debugger::IR { +class Lexer { +public: + bool Lex(char const *IR); + +private: +}; + +} diff --git a/Source/Tools/Debugger/Main.cpp b/Source/Tools/Debugger/Main.cpp new file mode 100644 index 0000000000..2c9a13f11a --- /dev/null +++ b/Source/Tools/Debugger/Main.cpp @@ -0,0 +1,207 @@ +#include "Common/ArgumentLoader.h" +#include "Common/Config.h" +#include "CommonCore/VMFactory.h" +#include "Tests/HarnessHelpers.h" +#include "MainWindow.h" +#include "Context.h" +#include "GLUtils.h" +#include "IMGui.h" +#include "DebuggerState.h" +#include "LogManager.h" + +#include "Event.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" + +Event SteppingEvent; +FEXCore::Context::Context *CTX{}; +FEXCore::SHM::SHMObject *SHM{}; +std::atomic_bool ShouldClose = false; +std::thread CoreThread; + +void CoreWorker() { + while (!ShouldClose) { + SteppingEvent.Wait(); + if (ShouldClose) { + break; + } + FEXCore::Context::RunLoop(CTX, false); + FEX::DebuggerState::SetHasNewState(); + FEX::DebuggerState::CallNewState(); + } +} + +void StepCallback() { + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, 1); + SteppingEvent.NotifyAll(); +} + +void CreateCoreCallback(char const *Filename, bool ELF) { + SHM = FEXCore::SHM::AllocateSHMRegion(1ULL << 36); + CTX = FEXCore::Context::CreateNewContext(); + + FEXCore::Context::AddGuestMemoryRegion(CTX, SHM); + + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, 1); + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_DEFAULTCORE, FEX::DebuggerState::GetCoreType()); + FEXCore::Context::SetFallbackCPUBackendFactory(CTX, VMFactory::CPUCreationFactoryFallback); + + FEXCore::Context::InitializeContext(CTX); + + bool Result{}; + if (ELF) { + FEX::HarnessHelper::ELFCodeLoader Loader{Filename, {}}; + Result = FEXCore::Context::InitCore(CTX, &Loader); + } + else { + std::string ConfigName = Filename; + ConfigName.erase(ConfigName.end() - 3, ConfigName.end()); + ConfigName += "config.bin"; + LogMan::Msg::I("Opening '%s'", Filename); + LogMan::Msg::I("Opening '%s'", ConfigName.c_str()); + FEX::HarnessHelper::HarnessCodeLoader Loader{Filename, ConfigName.c_str()}; + Result = FEXCore::Context::InitCore(CTX, &Loader); + } + + LogMan::Throw::A(Result, "Couldn't initialize CTX"); + + FEX::DebuggerState::SetContext(CTX); + FEX::DebuggerState::SetHasNewState(); + FEX::DebuggerState::CallNewState(); + CoreThread = std::thread(CoreWorker); +} + +void CompileRIPCallback(uint64_t RIP) { + FEXCore::Context::Debug::CompileRIP(CTX, RIP); + FEX::DebuggerState::SetHasNewState(); + FEX::DebuggerState::CallNewState(); +} + +void CloseCallback() { + ShouldClose = true; + SteppingEvent.NotifyAll(); + if (CoreThread.joinable()) { + CoreThread.join(); + } + + if (SHM) { + FEXCore::SHM::DestroyRegion(SHM); + } + + if (CTX) { + FEXCore::Context::DestroyContext(CTX); + } + + SHM = nullptr; + CTX = nullptr; + + ShouldClose = false; + FEX::DebuggerState::SetContext(CTX); + FEX::DebuggerState::SetHasNewState(); + FEX::DebuggerState::CallNewState(); +} + +void PauseCallback() { + FEXCore::Context::Pause(CTX); + FEX::DebuggerState::SetHasNewState(); + FEX::DebuggerState::CallNewState(); +} + +void ContinueCallback() { + FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, FEX::DebuggerState::GetRunningMode()); + SteppingEvent.NotifyAll(); +} + +void GetIRCallback(std::stringstream *out, uint64_t PC) { + if (!CTX) { + *out << ""; + return; + } + // FEXCore::IR::IntrusiveIRList *ir; + // bool HadIR = FEXCore::Context::Debug::FindIRForRIP(FEX::DebuggerState::GetContext(), PC, &ir); + // if (HadIR) { + // FEXCore::IR::Dump(out, ir); + // } + // else { + // *out << ""; + // } +} + +int main(int argc, char **argv) { + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + FEXCore::Context::InitializeStaticTables(); + + auto Context = GLContext::CreateContext(); + Context->Create("FEX Debugger"); + FEX::Debugger::Init(); + FEX::DebuggerState::RegisterStepCallback(StepCallback); + FEX::DebuggerState::RegisterPauseCallback(PauseCallback); + FEX::DebuggerState::RegisterContinueCallback(ContinueCallback); + FEX::DebuggerState::RegisterCreateCallback(CreateCoreCallback); + FEX::DebuggerState::RegisterCompileRIPCallback(CompileRIPCallback); + + FEX::DebuggerState::RegisterCloseCallback(CloseCallback); + FEX::DebuggerState::RegisterGetIRCallback(GetIRCallback); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + + GLFWwindow *Window = static_cast(Context->GetWindow()); + // Setup Platform/Renderer bindings + ImGui_ImplGlfw_InitForOpenGL(Window, true); + const char* glsl_version = "#version 410"; + ImGui_ImplOpenGL3_Init(glsl_version); + + while (!glfwWindowShouldClose(Window)) { + glfwPollEvents(); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + FEX::Debugger::DrawDebugUI(Context.get()); + + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + GLFWwindow* backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); + } + + Context->Swap(); + } + + FEX::Debugger::Shutdown(); + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + Context->Shutdown(); + + FEX::Config::Shutdown(); + + return 0; +} diff --git a/Source/Tools/Debugger/MainWindow.cpp b/Source/Tools/Debugger/MainWindow.cpp new file mode 100644 index 0000000000..600391a008 --- /dev/null +++ b/Source/Tools/Debugger/MainWindow.cpp @@ -0,0 +1 @@ +#include "MainWindow.h" diff --git a/Source/Tools/Debugger/MainWindow.h b/Source/Tools/Debugger/MainWindow.h new file mode 100644 index 0000000000..415d7155ff --- /dev/null +++ b/Source/Tools/Debugger/MainWindow.h @@ -0,0 +1,3 @@ +#pragma once +#include + diff --git a/Source/Tools/Debugger/Util/DataRingBuffer.h b/Source/Tools/Debugger/Util/DataRingBuffer.h new file mode 100644 index 0000000000..4023972ad1 --- /dev/null +++ b/Source/Tools/Debugger/Util/DataRingBuffer.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +namespace FEX::Debugger::Util { + template + class DataRingBuffer { + public: + DataRingBuffer(size_t Size) + : RingSize {Size} + , BufferSize {RingSize * SIZE_MULTIPLE} { + Data.resize(BufferSize); + } + + T const* operator()() const { + return &Data.at(ReadOffset); + } + + T back() const { + return Data.at(WriteOffset - 1); + } + + bool empty() const { + return WriteOffset == 0; + } + + size_t size() const { + return WriteOffset - ReadOffset; + } + + void push_back(T Val) { + if (WriteOffset + 1 >= BufferSize) { + // We reached the end of the buffer size. Time to wrap around + // Copy the ring buffer expected size to the start + memcpy(&Data.at(0), &Data.at(ReadOffset), sizeof(T) * RingSize); + WriteOffset = RingSize; + ReadOffset = 0; + } + Data.at(WriteOffset) = Val; + ++WriteOffset; + if (WriteOffset - ReadOffset > RingSize) { + ++ReadOffset; + } + } + + private: + constexpr static ssize_t SIZE_MULTIPLE = 3; + size_t RingSize; + size_t WriteOffset{}; + size_t ReadOffset{}; + size_t BufferSize; + std::vector Data; + }; +} diff --git a/Source/Tools/Opt.cpp b/Source/Tools/Opt.cpp new file mode 100644 index 0000000000..5be21258ec --- /dev/null +++ b/Source/Tools/Opt.cpp @@ -0,0 +1,11 @@ +#include "Common/ArgumentLoader.h" +#include "Common/Config.h" + +#include + +int main(int argc, char **argv) { + FEX::Config::Init(); + FEX::ArgLoader::Load(argc, argv); + + FEX::Config::Shutdown(); +} diff --git a/docs/Debugger.md b/docs/Debugger.md new file mode 100644 index 0000000000..70c5c10a20 --- /dev/null +++ b/docs/Debugger.md @@ -0,0 +1,30 @@ +# FEX - Debugger GUI +--- +FEX has a debugger GUI for debugging the translation of x86 code. +This UI is written in [Dear ImGui](https://github.com/ocornut/imgui) since it is purely for debugging and not user facing. + +## Features +* Stopping code execution at any point +* Single stepping code +* ELF code loading +* Test Harness code loading +* CPU State inspection +* IR inspection +* CF inspection of IR +* Application stderr, stdout viewing +* Disassembler for viewing both guest and host code + +## Nice to have +* IR writing and direct compiling + * Requires writing an IR lexer +* Profiling of compiled blocks directly in debugger + * Save CPU state prior to running, time compiled block for microprofiling +* Save all state to file to allow offline inspection and profiling + * Save original code + * Save CPU state on entry + * Save IR + * Save compiled host code + * Profiling data + * Allow comparison of all state before(What was saved to file) and after (New version compiled in this version of FEX for iterative debugging) +* Single stepping of IR state and inspection of SSA values +* Complete disconnection of debugger from FEXCore to ensure robustness if core crashes. diff --git a/docs/Diagram.svg b/docs/Diagram.svg new file mode 100644 index 0000000000..f6361ec465 --- /dev/null +++ b/docs/Diagram.svg @@ -0,0 +1,3 @@ + + +
FEX
[Not supported by viewer]
FEXCore
<font style="font-size: 36px" color="#e6e6e6">FEXCore</font>
CPU0
CPU1
...
[Not supported by viewer]
FEXCore API
[Not supported by viewer]
Syscall
Emulation
[Not supported by viewer]
Context
Context
ELFLoader
[Not supported by viewer]
Lockstep
[Not supported by viewer]
TestHarness
[Not supported by viewer]
Filesystem Emulation
Filesystem Emulation
Int
Int
JIT
JIT
LLVM
LLVM
CPUFactory
[Not supported by viewer]
Fallback
[Not supported by viewer]
VM
[Not supported by viewer]
Symbol DB
[Not supported by viewer]
\ No newline at end of file diff --git a/docs/VMCore.md b/docs/VMCore.md new file mode 100644 index 0000000000..6b244975f4 --- /dev/null +++ b/docs/VMCore.md @@ -0,0 +1,17 @@ +# FEX - Virtual Machine Custom CPUBackend +--- +This is a custom CPU backend that is used by the FEX frontend for its Lockstep Runner. +This is used for hardware validation of CPU emulation of the FEXCore's emulation. + +## Implementation details +This is implemented using the VM helper library that is living inside of [SonicUtils](https://github.com/Sonicadvance1/SonicUtils). +The helper library sets up a VM using KVM under Linux. + +## Limitations +* Only works under KVM + * I don't care about running under Windows, MacOS, or other hypervisors +* Only works under AMD + * Haven't spent time figuring out why it breaks, Intel throws a VMX invalid state entry error. +* Can't launch a bunch of instances due to memory limitations + * Each VM allocates the full VM's memory region upon initialization + * If you launch a VM with 64GB of memory then that full amount is allocated right away diff --git a/unittests/ASM/CALL.asm b/unittests/ASM/CALL.asm new file mode 100644 index 0000000000..19a05188e2 --- /dev/null +++ b/unittests/ASM/CALL.asm @@ -0,0 +1,36 @@ +%ifdef CONFIG +{ + "Ignore": [], + "RegData": { + "RAX": "1", + "RDX": "2" + }, + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +jmp label +label: + +mov rsp, 0xe8000000 + +; Test direct literal call +call function + +; Move the absolute address of function2 in to rbx and call it +mov rbx, function2+1 +call rbx + +hlt + +function: +mov rax, 1 +ret + +function2: +mov rbx, 2 +ret + +hlt diff --git a/unittests/ASM/CMakeLists.txt b/unittests/ASM/CMakeLists.txt new file mode 100644 index 0000000000..59db512453 --- /dev/null +++ b/unittests/ASM/CMakeLists.txt @@ -0,0 +1,45 @@ + +# Careful. Globbing can't see changes to the contents of files +# Need to do a fresh clean to see changes +file(GLOB ASM_SOURCES CONFIGURE_DEPENDS *.asm) + +set(ASM_DEPENDS "") +foreach(ASM_SRC ${ASM_SOURCES}) + get_filename_component(ASM_NAME ${ASM_SRC} NAME) + + # Generate a temporary file + set(ASM_TMP "${ASM_NAME}_TMP.asm") + set(TMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/${ASM_TMP}") + add_custom_command(OUTPUT ${TMP_FILE} + DEPENDS "${ASM_SRC}" + COMMAND "cp" ARGS "${ASM_SRC}" "${TMP_FILE}" + COMMAND "sed" ARGS "-i" "-e" "\'1s;^;BITS 64\\n;\'" "-e" "\'\$\$a\\ret\\n\'" "${TMP_FILE}" + ) + + set(OUTPUT_NAME "${ASM_NAME}.bin") + set(OUTPUT_CONFIG_NAME "${ASM_NAME}.config.bin") + + add_custom_command(OUTPUT ${OUTPUT_NAME} + DEPENDS "${TMP_FILE}" + COMMAND "nasm" ARGS "${TMP_FILE}" "-o" "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}") + + add_custom_command(OUTPUT ${OUTPUT_CONFIG_NAME} + DEPENDS "${ASM_SRC}" + COMMAND "python3" ARGS "${CMAKE_SOURCE_DIR}/Scripts/json_asm_parse.py" "${ASM_SRC}" "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_CONFIG_NAME}") + + list(APPEND ASM_DEPENDS "${OUTPUT_NAME};${OUTPUT_CONFIG_NAME}") + + set(TEST_NAME "Test_${ASM_NAME}") + add_test(NAME ${TEST_NAME} + COMMAND "${CMAKE_BINARY_DIR}/Bin/TestHarness" "${OUTPUT_NAME}" "${OUTPUT_CONFIG_NAME}") + # This will cause the ASM tests to fail if it can't find the TestHarness or ASMN files + # Prety crap way to work around the fact that tests can't have a build dependency in a different directory + # Just make sure to independently run `make all` then `make test` + set_property(TEST ${TEST_NAME} APPEND PROPERTY DEPENDS "${CMAKE_BINARY_DIR}/Bin/TestHarness") + set_property(TEST ${TEST_NAME} APPEND PROPERTY DEPENDS "${OUTPUT_NAME}") + set_property(TEST ${TEST_NAME} APPEND PROPERTY DEPENDS "${OUTPUT_CONFIG_NAME}") + +endforeach() + +add_custom_target(asm_files ALL + DEPENDS "${ASM_DEPENDS}") diff --git a/unittests/ASM/JMP.asm b/unittests/ASM/JMP.asm new file mode 100644 index 0000000000..92ae44b040 --- /dev/null +++ b/unittests/ASM/JMP.asm @@ -0,0 +1,55 @@ +%ifdef CONFIG +{ + "Ignore": [], + "RegData": { + "RAX": "1", + "RBX": "2", + "RCX": "3", + "RDX": "4" + }, + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +jmp label +label: + +mov rsp, 0xe8000000 + +jmp function +func_return: + +mov rbx, function2+1 +jmp rbx +func2_return: + +cmp rcx, rcx +je function3 +func3_return: + +mov rdx, 4 +jne function4 +func4_return: + +hlt + +function: +mov rax, 1 +jmp func_return + +function2: +mov rbx, 2 +jmp func2_return + +function3: +mov rcx, 3 +jmp func3_return + +function4: +mov rdx, 0xDEADBEEF +jmp func4_return + +hlt + diff --git a/unittests/ASM/STOS.asm b/unittests/ASM/STOS.asm new file mode 100644 index 0000000000..7410fe6976 --- /dev/null +++ b/unittests/ASM/STOS.asm @@ -0,0 +1,61 @@ +%ifdef CONFIG +{ + "Match": "All", + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +; Data we want to store +mov rax, 0xDEADBEEFBAD0DAD1 + +; Starting address to store to +mov rdi, 0xe8000000 + +; How many elements we want to store +mov rcx, 0x0 + +; Direction to increment (Increment when cleared) +cld + +; Store bytes +rep stosw + +mov r11, 0 +mov r10, 0xe8000000 + +movzx r12, word [r10 + 0] +add r11, r12 +movzx r12, word [r10 + 1] +add r11, r12 +movzx r12, word [r10 + 2] +add r11, r12 +movzx r12, word [r10 + 3] +add r11, r12 +movzx r12, word [r10 + 4] +add r11, r12 +movzx r12, word [r10 + 5] +add r11, r12 +movzx r12, word [r10 + 6] +add r11, r12 +movzx r12, word [r10 + 7] +add r11, r12 +movzx r12, word [r10 + 8] +add r11, r12 +movzx r12, word [r10 + 9] +add r11, r12 +movzx r12, word [r10 + 10] +add r11, r12 +movzx r12, word [r10 + 11] +add r11, r12 +movzx r12, word [r10 + 12] +add r11, r12 +movzx r12, word [r10 + 13] +add r11, r12 +movzx r12, word [r10 + 14] +add r11, r12 +movzx r12, word [r10 + 15] +add r11, r12 + +hlt diff --git a/unittests/ASM/jump.asm b/unittests/ASM/jump.asm new file mode 100644 index 0000000000..d7a59ebcf0 --- /dev/null +++ b/unittests/ASM/jump.asm @@ -0,0 +1,35 @@ +%ifdef CONFIG +{ + "Match": "All", + "RegData": { + "RAX": "0x1" + } +} +%endif + +mov esi, 50 + +.jump_start: +mov edi, 1 +test edi, edi +nop +nop +nop +nop +nop +nop +nop +nop + +jz .local +mov eax, 1 +jmp .end + +.local: +mov eax, 0 + +.end: +sub esi, 1 +test esi, esi +jz .jump_start +hlt diff --git a/unittests/ASM/lea.asm b/unittests/ASM/lea.asm new file mode 100644 index 0000000000..66e5cf186b --- /dev/null +++ b/unittests/ASM/lea.asm @@ -0,0 +1,16 @@ +%ifdef CONFIG +{ + "Match": "All", + "RegData": { + "RAX": "0x1BD5B7DDE", + "RBX": "0x0DEADBF18" + } +} +%endif + +mov r15, 0xDEADBEEF +mov r14, 0x5 + +lea rax, [r15*2] +lea rbx, [r15+r14*8 + 1] + diff --git a/unittests/ASM/mov.asm b/unittests/ASM/mov.asm new file mode 100644 index 0000000000..b6c0520847 --- /dev/null +++ b/unittests/ASM/mov.asm @@ -0,0 +1,36 @@ +%ifdef CONFIG +{ + "Match": "All", + "RegData": { + "RAX": "0xFFFFFFFFFFFFFFD1", + "RBX": "0xFFFFFFFFFFFFDAD1", + "RCX": "0xBAD0DAD1", + "RDX": "0xDEADBEEFBAD0DAD1", + "R15": "0xDEADBEEFBAD0DAD1" + } +} +%endif + +mov rax, -1 +mov rbx, -1 +mov rcx, -1 + +mov r15, qword 0xDEADBEEFBAD0DAD1 +mov rdx, qword 0xDEADBEEFBAD0DAD1 + +;mov al, dl +;mov bx, dx +;mov ecx, edx +;mov al, -1 +;mov ax, -1 +;mov eax, -1 +;mov rax, qword -1 +;mov rax, 0 +;mov al, al +;mov rbx, -1 +;mov bx, ax +;mov ax, ax +;mov ax, ax +;mov eax, eax +;mov rax, rax +hlt diff --git a/unittests/ASM/movups.asm b/unittests/ASM/movups.asm new file mode 100644 index 0000000000..1741ce459e --- /dev/null +++ b/unittests/ASM/movups.asm @@ -0,0 +1,66 @@ +%ifdef CONFIG +{ + "Ignore": ["RAX", "RDX"], + "RegData": { + "RAX" : "0x0000FFFF", + "XMM0": ["0x3f800000", "0x40000000"], + "XMM1": ["0x3f800000", "0x40000000"], + "XMM2": ["0x3f800000", "0x40000000"], + "XMM3": ["0x3f800000", "0x8100000080000000"], + "XMM4": ["0xDEADBEEFBFD0DAD1", "0x4141414142424242"], + "XMM5": ["0xDEADBEEFBAD0DAD1", "0"], + "XMM6": ["0xDEADBEEFBFD0DAD1", "0"] + }, + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +jmp label +label: +mov rax, 0x3f800000 +;mov rsi, 0xdeadbeefbaddad1 +;rdseed eax +;vpermd ymm0, ymm1, ymm2 +mov rdx, 0xe0000000 +mov [rdx], eax +mov eax, 0x40000000 +mov [rdx + 8], eax + +movups xmm0, [rdx] +movups xmm1, xmm0 + +movups [rdx + 16], xmm1 +movups xmm2, [rdx + 16] + +; Upper moves +mov eax, 0x80000000 + +mov [rdx + 32], eax +mov eax, 0x81000000 +mov [rdx + 36], eax + +movups xmm3, xmm0 +movhps xmm3, [rdx + 32] + +mov rax, 0xDEADBEEFBAD0DAD1 +mov [rdx + 32], rax +mov rax, 0x4141414142424242 +mov [rdx + 40], rax +movups xmm4, [rdx + 32] +movq xmm5, [rdx + 32] + +por xmm4, xmm0 +movq xmm6, xmm4 +paddq xmm7, xmm6 +mov rax, 0xFFFFFFFFFFFFFFFF +mov [rdx + 32], rax +mov [rdx + 40], rax +movdqu xmm8, [rdx + 32] +pmovmskb eax, xmm8 + +;fcomp dword [0] +;fldl2t +;fld1 +hlt diff --git a/unittests/ASM/movzx.asm b/unittests/ASM/movzx.asm new file mode 100644 index 0000000000..c3f9ed8609 --- /dev/null +++ b/unittests/ASM/movzx.asm @@ -0,0 +1,23 @@ +%ifdef CONFIG +{ + "Match": "All", + "RegData": { + "RBX": "0xFFFFFFFFFFFF00D1", + "RCX": "0x00000000000000D1", + "RDX": "0xDAD1", + "RDI": "0xDAD1" + } +} +%endif + +mov rax, qword 0xDEADBEEFBAD0DAD1 + +mov rbx, -1 +mov rcx, -1 +mov rdx, -1 +mov rdi, -1 + +movzx bx, al ; 8bit-> 16bit +movzx ecx, al ; 8bit-> 32bit +movzx edx, ax ; 16bit-> 32bit +movzx rdi, ax ; 16bit -> 64bit diff --git a/unittests/ASM/mul.asm b/unittests/ASM/mul.asm new file mode 100644 index 0000000000..9fc66fe0d0 --- /dev/null +++ b/unittests/ASM/mul.asm @@ -0,0 +1,17 @@ +%ifdef CONFIG +{ + "Match": "All", + "RegData": { + "RAX": "0xFFFFFFFFFFFFFFFE", + "RDX": "1", + "RBX": "1" + } +} +%endif +mov rax, -1 +mov rdx, 0 +mov rbx, 1 + +mul rbx + +hlt diff --git a/unittests/ASM/simple_loop.asm b/unittests/ASM/simple_loop.asm new file mode 100644 index 0000000000..f428bd9a0a --- /dev/null +++ b/unittests/ASM/simple_loop.asm @@ -0,0 +1,42 @@ +%ifdef CONFIG +{ + "Ignore": [], + "RegData": { + "RAX": "1", + "RDX": "2" + }, + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +mov rsp, 0xe8000000 +mov rdi, 0xe0000000 +call .simple_loop + + +hlt + +.simple_loop: +pxor xmm0, xmm0 +pxor xmm1, xmm1 +mov rax, 0xffffffffffffd8f0 + +.loop_top: +movdqu xmm2, [rdi + rax * 4 + 0x9c40] +paddd xmm0, xmm2 +movdqu xmm2, [rdi + rax * 4 + 0x9c50] +paddd xmm1, xmm2 + +add rax, 8 +jne .loop_top + +paddd xmm1, xmm0 +pshufd xmm0, xmm1, 0x4e +paddd xmm0, xmm1 +pshufd xmm1, xmm0, 0xe5 +paddd xmm1, xmm0 +movd eax, xmm1 + +retq diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt new file mode 100644 index 0000000000..c0815455f4 --- /dev/null +++ b/unittests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ASM/) diff --git a/unittests/Example.asm b/unittests/Example.asm new file mode 100644 index 0000000000..e39648c4ba --- /dev/null +++ b/unittests/Example.asm @@ -0,0 +1,64 @@ +; If you want a specific configuration at the top of asm file then make sure to wrap it in ifdef and endif. +; This allows the python script to extract the json and nasm to ignore the section +; +; X86 State option that can be compared +; - All: Makes sure all options are compared +; - None: No options +; ===== Specific options ==== +; -- GPRs -- +; RAX, RBX, RCX, RDX +; RSI, RDI, RBP, RSP +; R8-R15 +; -- XMM -- +; XMM0-XX15 +; -- Misc -- +; RIP +; FS, GS +; Flags +; =========================== +; Match: Forces full matching of types +; - Type: String or List of strings +; - Default: All +; Ignore: Forces types to be ignored when matching. Overwrites Matches +; - Default: None +; - Type: String or List of strings +; RegData: Makes sure that a register contains specific data +; - Default: Any data +; - Type: Dict of key:value pairs +; - >64bit registers should contain a list of values for each 64bit value +; +; Additional config options +; ABI : {SystemV-64, Win64, None} +; - Default: SystemV-64 +; StackSize : Stack size that the test needs +; - Default : 4096 +; - Stack address starts at: [0xc000'0000, 0xc000'0000 + StackSize) +; EntryPoint : Entrypoint for the assembly +; - Default: 1 +; - 0 is invalid since that is special cased +; MemoryRegions: Memory Regions for the tests to use +; - Default: No memory regions generated +; - Dict of key:value pairs +; - Key indicates the memory base +; - Value indicates the memory region size +; - WARNING: Emulator sets up some default regions that you don't want to intersect with +; - Additionally the VM only has 64GB of virtual memory. If you go past this sizer, expect failure +; - 0xb000'0000 - FS Memory base +; - 0xc000'0000 - Stack pointer base +; - 0xd000'0000 - Linux BRK memory base + +%ifdef CONFIG +{ + "Match": "All", + "Ignore": ["XMM0", "Flags"], + "RegData": { + "RAX": "1" + }, + "MemoryRegions": { + "0x100000000": "4096" + } +} +%endif + +mov eax, 1 +ret