Skip to content

Commit

Permalink
Linux: Converts passthrough syscalls to generator script
Browse files Browse the repository at this point in the history
After adding the past few Linux versions of syscalls I noticed that this
could be better.

Walks through all of our syscalls and converts them to a json format
that is parsed and then generated at compile time.

This also fixes some mislabeling of system calls that were getting
inlined on accident.

This really helps clean up the syscall implementation, all syscalls that
can get passed through to the host syscall handler is no longer visible
in the implementation.
  • Loading branch information
Sonicadvance1 committed Jan 3, 2024
1 parent 3c384fa commit 89f6570
Show file tree
Hide file tree
Showing 45 changed files with 2,586 additions and 2,154 deletions.
220 changes: 220 additions & 0 deletions Scripts/GenerateSyscallHandlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
#!/usr/bin/python3
from dataclasses import dataclass, field
import json
import struct
import sys
from json_config_parse import parse_json
import logging
logger = logging.getLogger()
logger.setLevel(logging.WARNING)

@dataclass
class SyscallDefinition:
Name: str
CustomHandler: bool
HandlerType: str
ArgCount: int
Flags: str
MinimumKernel: str
SyscallRedirect: str

def __init__(self, Name, CustomHandler, HandlerType, ArgCount, Flags, MinimumKernel, SyscallRedirect):
self.Name = Name
self.CustomHandler = CustomHandler
self.HandlerType = HandlerType
self.ArgCount = ArgCount
self.Flags = Flags
self.MinimumKernel = MinimumKernel
self.SyscallRedirect = SyscallRedirect

SyscallDefinitionsCommon = {}
SyscallDefinitionsx64 = {}
SyscallDefinitionsx32 = {}

def ParseTable(json_object, table, name):
data = json_object[name]
for data_key, data_val in data.items():
name = data_key

argcount = 0
flags = ""
MinimumKernel = ""
SyscallRedirect = ""
CustomHandler = False

if "CustomHandler" in data_val:
CustomHandler = bool(data_val["CustomHandler"])

if "ArchSpecific" in data_val:
if bool(data_val["ArchSpecific"]):
continue

if not CustomHandler:
if not "ArgCount" in data_val:
logging.critical("Syscall {} doesn't have argument count".format(name))

if not "Flags" in data_val:
logging.critical("Syscall {} doesn't have flags".format(name))

argcount = data_val["ArgCount"]
flags = data_val["Flags"]

if "MinimumKernel" in data_val:
MinimumKernel = data_val["MinimumKernel"]

Splits = MinimumKernel.split(".")
if len(Splits) != 3:
logging.critical("Syscall {} has invalid kernel version as '{}'. Expecting format 'Major.Minor.Patch'".format(name, MinimumKernel))

if "SyscallRedirect" in data_val:
SyscallRedirect = data_val["SyscallRedirect"]

table[name] = SyscallDefinition(name, CustomHandler, "Common", argcount, flags, MinimumKernel, SyscallRedirect)

def ParseJson(JsonText):
json_object = json.loads(JsonText)

if not "Common" in json_object:
logging.critical ("Need to have common syscalls")

if not "x64" in json_object:
logging.critical ("Need to have x64 syscalls")

if not "x32" in json_object:
logging.critical ("Need to have x32 syscalls")

ParseTable(json_object, SyscallDefinitionsCommon, "Common")
ParseTable(json_object, SyscallDefinitionsx64, "x64")
ParseTable(json_object, SyscallDefinitionsx32, "x32")

def PrintHandlers(output_file, wrapper_name, table, impl_flags, impl, redirect_impl_flags, redirect_impl):
output_file.write("#ifdef {}\n".format(wrapper_name))
output_file.write("#undef {}\n".format(wrapper_name))
for data_key, data_val in table.items():
if data_val.CustomHandler:
continue

HasMinimumKernel = data_val.MinimumKernel != ""
HasSyscallRedirect = data_val.SyscallRedirect != ""

which_impl_flags = impl_flags
which_impl = impl

if HasSyscallRedirect:
which_impl_flags = redirect_impl_flags
which_impl = redirect_impl

if HasMinimumKernel:
Splits = data_val.MinimumKernel.split(".")
output_file.write ("if (Handler->IsHostKernelVersionAtLeast({}, {}, {})) {{\n".format(Splits[0], Splits[1], Splits[2]))

if HasSyscallRedirect:
output_file.write ("{}({}, {}, {},\n".format(which_impl_flags, data_key, data_val.SyscallRedirect, data_val.Flags))
else:
output_file.write ("{}({}, {},\n".format(which_impl_flags, data_key, data_val.Flags))
output_file.write (" SyscallPassthrough{}<SYSCALL_DEF({})>);\n".format(data_val.ArgCount, data_key))

if HasMinimumKernel:
output_file.write ("}\n")
output_file.write ("else {\n")

output_file.write (" {}({}, UnimplementedSyscallSafe);\n".format(which_impl, data_key))
output_file.write ("}\n")

output_file.write("#endif\n")

def PrintPassthroughHandlers(output_file):
output_file.write("#ifdef PASSTHROUGH_HANDLERS\n")
output_file.write("#undef PASSTHROUGH_HANDLERS\n")

output_file.write("#ifdef _M_ARM_64\n")
for i in range(0, 8):
output_file.write("template<int syscall_num>\n");
output_file.write("uint64_t SyscallPassthrough{}(FEXCore::Core::CpuStateFrame *Frame".format(i))

for j in range(0, i):
output_file.write(", uint64_t arg{}".format(j + 1))

output_file.write(") {\n")
if i > 0:
for j in range(0, i):
output_file.write(" register uint64_t x{} asm (\"x{}\") = arg{};\n".format(j, j, j + 1))
else:
output_file.write(" register uint64_t x0 asm (\"x0\");\n")

output_file.write(" register int x8 asm (\"x8\") = syscall_num;\n");
output_file.write(" __asm volatile(R\"(\n")
output_file.write(" svc #0;\n")
output_file.write(" )\"\n")
output_file.write(" : \"=r\" (x0)\n")
output_file.write(" : \"r\" (x8)\n")
for j in range(0, i):
output_file.write(" , \"r\" (x{})\n".format(j))

output_file.write(" : \"memory\");\n")

output_file.write(" return x0;\n")
output_file.write("}\n\n")

output_file.write("#else\n")

for i in range(0, 8):
output_file.write("template<int syscall_num>\n")
output_file.write("uint64_t SyscallPassthrough{}(FEXCore::Core::CpuStateFrame *Frame".format(i))

for j in range(0, i):
output_file.write(", uint64_t arg{}".format(j + 1))

output_file.write(") {\n")

output_file.write(" uint64_t Result = ::syscall(syscall_num")
for j in range(0, i):
output_file.write(", arg{}".format(j + 1))

output_file.write(");\n")

output_file.write(" SYSCALL_ERRNO();\n")
output_file.write("}\n")
output_file.write("#endif\n")
output_file.write("#endif\n")

def main():
if sys.version_info[0] < 3:
logging.critical ("Python 3 or a more recent version is required.")

if (len(sys.argv) < 3):
print ("usage: %s <Syscall description json> <output file>" % (sys.argv[0]))

JsonDescPath = sys.argv[1]
OutputFilePath = sys.argv[2]

JsonFile = open(JsonDescPath, "r")
JsonText = JsonFile.read()
JsonFile.close()

ParseJson(JsonText)

output_file = open(OutputFilePath, "w")
PrintPassthroughHandlers(output_file)

PrintHandlers(output_file,
"SYSCALL_COMMON_IMPL",
SyscallDefinitionsCommon,
"REGISTER_SYSCALL_IMPL_PASS_FLAGS", "REGISTER_SYSCALL_IMPL",
"<UNSUPPORTED>", "<UNSUPPORTED>")
PrintHandlers(output_file,
"SYSCALL_X64_IMPL",
SyscallDefinitionsx64,
"REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS", "REGISTER_SYSCALL_IMPL_X64",
"<UNSUPPORTED>", "<UNSUPPORTED>")
PrintHandlers(output_file,
"SYSCALL_X32_IMPL",
SyscallDefinitionsx32,
"REGISTER_SYSCALL_IMPL_X32_PASS_FLAGS", "REGISTER_SYSCALL_IMPL_X32",
"REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS", "REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL")

output_file.close()

if __name__ == "__main__":
# execute only if run as a script
sys.exit(main())
28 changes: 14 additions & 14 deletions Source/Tools/LinuxEmulation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,40 @@ set (SRCS
LinuxSyscalls/x32/IoctlEmulation.cpp
LinuxSyscalls/x64/EPoll.cpp
LinuxSyscalls/x64/FD.cpp
LinuxSyscalls/x64/IO.cpp
LinuxSyscalls/x64/Ioctl.cpp
LinuxSyscalls/x64/Info.cpp
LinuxSyscalls/x64/Memory.cpp
LinuxSyscalls/x64/Msg.cpp
LinuxSyscalls/x64/NotImplemented.cpp
LinuxSyscalls/x64/Semaphore.cpp
LinuxSyscalls/x64/Sched.cpp
LinuxSyscalls/x64/Signals.cpp
LinuxSyscalls/x64/Socket.cpp
LinuxSyscalls/x64/Thread.cpp
LinuxSyscalls/x64/Syscalls.cpp
LinuxSyscalls/x64/Time.cpp
LinuxSyscalls/Syscalls/EPoll.cpp
LinuxSyscalls/Syscalls/FD.cpp
LinuxSyscalls/Syscalls/FS.cpp
LinuxSyscalls/Syscalls/Generated.cpp
LinuxSyscalls/Syscalls/Info.cpp
LinuxSyscalls/Syscalls/IO.cpp
LinuxSyscalls/Syscalls/IOUring.cpp
LinuxSyscalls/Syscalls/Key.cpp
LinuxSyscalls/Syscalls/Memory.cpp
LinuxSyscalls/Syscalls/Msg.cpp
LinuxSyscalls/Syscalls/Namespace.cpp
LinuxSyscalls/Syscalls/Sched.cpp
LinuxSyscalls/Syscalls/Semaphore.cpp
LinuxSyscalls/Syscalls/SHM.cpp
LinuxSyscalls/Syscalls/Signals.cpp
LinuxSyscalls/Syscalls/Socket.cpp
LinuxSyscalls/Syscalls/Thread.cpp
LinuxSyscalls/Syscalls/Time.cpp
LinuxSyscalls/Syscalls/Timer.cpp
LinuxSyscalls/Syscalls/NotImplemented.cpp
LinuxSyscalls/Syscalls/Stubs.cpp)

set(OUTPUT_LINUXSYSCALLS_FOLDER "${CMAKE_CURRENT_BINARY_DIR}/")

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/GeneratedSyscallHandlers.inl"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/SyscallDescription.json"
DEPENDS "${PROJECT_SOURCE_DIR}/Scripts/GenerateSyscallHandlers.py"
COMMAND "python3" ARGS "${PROJECT_SOURCE_DIR}/Scripts/GenerateSyscallHandlers.py"
"${CMAKE_CURRENT_SOURCE_DIR}/SyscallDescription.json"
"${CMAKE_CURRENT_BINARY_DIR}/GeneratedSyscallHandlers.inl")

add_custom_target(GeneratedSyscallHandlers DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/GeneratedSyscallHandlers.inl")

add_library(LinuxEmulation STATIC ${SRCS})
add_dependencies(LinuxEmulation GeneratedSyscallHandlers)

target_compile_options(LinuxEmulation
PRIVATE
Expand All @@ -82,6 +81,7 @@ PRIVATE
target_include_directories(LinuxEmulation
PRIVATE
${CMAKE_BINARY_DIR}/generated
${CMAKE_CURRENT_BINARY_DIR}/
${CMAKE_CURRENT_SOURCE_DIR}/
${PROJECT_SOURCE_DIR}/External/drm-headers/include/
)
Expand Down
10 changes: 1 addition & 9 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,14 @@ class SignalDelegator;
void RegisterFS(FEX::HLE::SyscallHandler *Handler);
void RegisterInfo(FEX::HLE::SyscallHandler *Handler);
void RegisterIO(FEX::HLE::SyscallHandler *Handler);
void RegisterIOUring(FEX::HLE::SyscallHandler *Handler);
void RegisterKey(FEX::HLE::SyscallHandler *Handler);
void RegisterMemory(FEX::HLE::SyscallHandler *Handler);
void RegisterMsg(FEX::HLE::SyscallHandler *Handler);
void RegisterNamespace(FEX::HLE::SyscallHandler *Handler);
void RegisterNuma(FEX::HLE::SyscallHandler *Handler);
void RegisterSched(FEX::HLE::SyscallHandler *Handler);
void RegisterSemaphore(FEX::HLE::SyscallHandler *Handler);
void RegisterSHM(FEX::HLE::SyscallHandler *Handler);
void RegisterSignals(FEX::HLE::SyscallHandler *Handler);
void RegisterSocket(FEX::HLE::SyscallHandler *Handler);
void RegisterThread(FEX::HLE::SyscallHandler *Handler);
void RegisterTime(FEX::HLE::SyscallHandler *Handler);
void RegisterTimer(FEX::HLE::SyscallHandler *Handler);
void RegisterNotImplemented(FEX::HLE::SyscallHandler *Handler);
void RegisterStubs(FEX::HLE::SyscallHandler *Handler);
void RegisterGenerated(FEX::HLE::SyscallHandler *Handler);

uint64_t UnimplementedSyscall(FEXCore::Core::CpuStateFrame *Frame, uint64_t SyscallNumber);
uint64_t UnimplementedSyscallSafe(FEXCore::Core::CpuStateFrame *Frame, uint64_t SyscallNumber);
Expand Down
9 changes: 1 addition & 8 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/EPoll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ meta: LinuxSyscalls|syscalls-shared ~ Syscall implementations shared between x86
tags: LinuxSyscalls|syscalls-shared
$end_info$
*/

#include "LinuxSyscalls/Syscalls.h"
#include "LinuxSyscalls/x64/Syscalls.h"
#include "LinuxSyscalls/x32/Syscalls.h"
Expand All @@ -19,16 +18,10 @@ namespace FEX::HLE {
void RegisterEpoll(FEX::HLE::SyscallHandler *Handler) {
using namespace FEXCore::IR;

REGISTER_SYSCALL_IMPL_PASS_FLAGS(epoll_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
REGISTER_SYSCALL_IMPL_FLAGS(epoll_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
[](FEXCore::Core::CpuStateFrame *Frame, int size) -> uint64_t {
uint64_t Result = epoll_create(size);
SYSCALL_ERRNO();
});

REGISTER_SYSCALL_IMPL_PASS_FLAGS(epoll_create1, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
[](FEXCore::Core::CpuStateFrame *Frame, int flags) -> uint64_t {
uint64_t Result = epoll_create1(flags);
SYSCALL_ERRNO();
});
}
}
Loading

0 comments on commit 89f6570

Please sign in to comment.