diff options
| -rw-r--r-- | src/core/hle/service/jit/jit.cpp | 205 | ||||
| -rw-r--r-- | src/core/hle/service/jit/jit_context.cpp | 151 | ||||
| -rw-r--r-- | src/core/hle/service/jit/jit_context.h | 1 |
3 files changed, 225 insertions, 132 deletions
diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index 185d0387b..8f2920c51 100644 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp | |||
| @@ -21,9 +21,10 @@ struct CodeRange { | |||
| 21 | 21 | ||
| 22 | class IJitEnvironment final : public ServiceFramework<IJitEnvironment> { | 22 | class IJitEnvironment final : public ServiceFramework<IJitEnvironment> { |
| 23 | public: | 23 | public: |
| 24 | explicit IJitEnvironment(Core::System& system_, CodeRange user_rx, CodeRange user_ro) | 24 | explicit IJitEnvironment(Core::System& system_, Kernel::KProcess& process_, CodeRange user_rx, |
| 25 | CodeRange user_ro) | ||
| 25 | : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew}, | 26 | : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew}, |
| 26 | context{system_.Memory()} { | 27 | process{&process_}, context{system_.Memory()} { |
| 27 | // clang-format off | 28 | // clang-format off |
| 28 | static const FunctionInfo functions[] = { | 29 | static const FunctionInfo functions[] = { |
| 29 | {0, &IJitEnvironment::GenerateCode, "GenerateCode"}, | 30 | {0, &IJitEnvironment::GenerateCode, "GenerateCode"}, |
| @@ -43,54 +44,80 @@ public: | |||
| 43 | } | 44 | } |
| 44 | 45 | ||
| 45 | void GenerateCode(Kernel::HLERequestContext& ctx) { | 46 | void GenerateCode(Kernel::HLERequestContext& ctx) { |
| 46 | struct Parameters { | 47 | LOG_DEBUG(Service_JIT, "called"); |
| 48 | |||
| 49 | struct InputParameters { | ||
| 47 | u32 data_size; | 50 | u32 data_size; |
| 48 | u64 command; | 51 | u64 command; |
| 49 | CodeRange cr1; | 52 | std::array<CodeRange, 2> ranges; |
| 50 | CodeRange cr2; | ||
| 51 | Struct32 data; | 53 | Struct32 data; |
| 52 | }; | 54 | }; |
| 53 | 55 | ||
| 56 | struct OutputParameters { | ||
| 57 | s32 return_value; | ||
| 58 | std::array<CodeRange, 2> ranges; | ||
| 59 | }; | ||
| 60 | |||
| 54 | IPC::RequestParser rp{ctx}; | 61 | IPC::RequestParser rp{ctx}; |
| 55 | const auto parameters{rp.PopRaw<Parameters>()}; | 62 | const auto parameters{rp.PopRaw<InputParameters>()}; |
| 63 | |||
| 64 | // Optional input/output buffers | ||
| 56 | std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; | 65 | std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; |
| 57 | std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); | 66 | std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); |
| 58 | 67 | ||
| 59 | const VAddr return_ptr{context.AddHeap(0u)}; | 68 | // Function call prototype: |
| 60 | const VAddr cr1_in_ptr{context.AddHeap(parameters.cr1)}; | 69 | // void GenerateCode(s32* ret, CodeRange* c0_out, CodeRange* c1_out, JITConfiguration* cfg, |
| 61 | const VAddr cr2_in_ptr{context.AddHeap(parameters.cr2)}; | 70 | // u64 cmd, u8* input_buf, size_t input_size, CodeRange* c0_in, |
| 62 | const VAddr cr1_out_ptr{ | 71 | // CodeRange* c1_in, Struct32* data, size_t data_size, u8* output_buf, |
| 63 | context.AddHeap(CodeRange{.offset = parameters.cr1.offset, .size = 0})}; | 72 | // size_t output_size); |
| 64 | const VAddr cr2_out_ptr{ | 73 | // |
| 65 | context.AddHeap(CodeRange{.offset = parameters.cr2.offset, .size = 0})}; | 74 | // The command argument is used to control the behavior of the plugin during code |
| 75 | // generation. The configuration allows the plugin to access the output code ranges, and the | ||
| 76 | // other arguments are used to transfer state between the game and the plugin. | ||
| 77 | |||
| 78 | const VAddr ret_ptr{context.AddHeap(0u)}; | ||
| 79 | const VAddr c0_in_ptr{context.AddHeap(parameters.ranges[0])}; | ||
| 80 | const VAddr c1_in_ptr{context.AddHeap(parameters.ranges[1])}; | ||
| 81 | const VAddr c0_out_ptr{context.AddHeap(ClearSize(parameters.ranges[0]))}; | ||
| 82 | const VAddr c1_out_ptr{context.AddHeap(ClearSize(parameters.ranges[1]))}; | ||
| 83 | |||
| 66 | const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; | 84 | const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; |
| 67 | const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; | 85 | const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; |
| 68 | const VAddr data_ptr{context.AddHeap(parameters.data)}; | 86 | const VAddr data_ptr{context.AddHeap(parameters.data)}; |
| 69 | const VAddr configuration_ptr{context.AddHeap(configuration)}; | 87 | const VAddr configuration_ptr{context.AddHeap(configuration)}; |
| 70 | 88 | ||
| 71 | context.CallFunction(callbacks.GenerateCode, return_ptr, cr1_out_ptr, cr2_out_ptr, | 89 | // The callback does not directly return a value, it only writes to the output pointer |
| 90 | context.CallFunction(callbacks.GenerateCode, ret_ptr, c0_out_ptr, c1_out_ptr, | ||
| 72 | configuration_ptr, parameters.command, input_ptr, input_buffer.size(), | 91 | configuration_ptr, parameters.command, input_ptr, input_buffer.size(), |
| 73 | cr1_in_ptr, cr2_in_ptr, data_ptr, parameters.data_size, output_ptr, | 92 | c0_in_ptr, c1_in_ptr, data_ptr, parameters.data_size, output_ptr, |
| 74 | output_buffer.size()); | 93 | output_buffer.size()); |
| 75 | 94 | ||
| 76 | const s32 return_value{context.GetHeap<s32>(return_ptr)}; | 95 | const s32 return_value{context.GetHeap<s32>(ret_ptr)}; |
| 77 | 96 | ||
| 78 | if (return_value == 0) { | 97 | if (return_value == 0) { |
| 98 | // The callback has written to the output executable code range, | ||
| 99 | // requiring an instruction cache invalidation | ||
| 79 | system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset, | 100 | system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset, |
| 80 | configuration.user_rx_memory.size); | 101 | configuration.user_rx_memory.size); |
| 81 | 102 | ||
| 103 | // Write back to the IPC output buffer, if provided | ||
| 82 | if (ctx.CanWriteBuffer()) { | 104 | if (ctx.CanWriteBuffer()) { |
| 83 | context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); | 105 | context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); |
| 84 | ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); | 106 | ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); |
| 85 | } | 107 | } |
| 86 | const auto cr1_out{context.GetHeap<CodeRange>(cr1_out_ptr)}; | 108 | |
| 87 | const auto cr2_out{context.GetHeap<CodeRange>(cr2_out_ptr)}; | 109 | const OutputParameters out{ |
| 110 | .return_value = return_value, | ||
| 111 | .ranges = | ||
| 112 | { | ||
| 113 | context.GetHeap<CodeRange>(c0_out_ptr), | ||
| 114 | context.GetHeap<CodeRange>(c1_out_ptr), | ||
| 115 | }, | ||
| 116 | }; | ||
| 88 | 117 | ||
| 89 | IPC::ResponseBuilder rb{ctx, 8}; | 118 | IPC::ResponseBuilder rb{ctx, 8}; |
| 90 | rb.Push(ResultSuccess); | 119 | rb.Push(ResultSuccess); |
| 91 | rb.Push<u64>(return_value); | 120 | rb.PushRaw(out); |
| 92 | rb.PushRaw(cr1_out); | ||
| 93 | rb.PushRaw(cr2_out); | ||
| 94 | } else { | 121 | } else { |
| 95 | LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed"); | 122 | LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed"); |
| 96 | IPC::ResponseBuilder rb{ctx, 2}; | 123 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -99,25 +126,40 @@ public: | |||
| 99 | }; | 126 | }; |
| 100 | 127 | ||
| 101 | void Control(Kernel::HLERequestContext& ctx) { | 128 | void Control(Kernel::HLERequestContext& ctx) { |
| 129 | LOG_DEBUG(Service_JIT, "called"); | ||
| 130 | |||
| 102 | IPC::RequestParser rp{ctx}; | 131 | IPC::RequestParser rp{ctx}; |
| 103 | const auto command{rp.PopRaw<u64>()}; | 132 | const auto command{rp.PopRaw<u64>()}; |
| 104 | const auto input_buffer{ctx.ReadBuffer()}; | 133 | |
| 134 | // Optional input/output buffers | ||
| 135 | std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; | ||
| 105 | std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); | 136 | std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); |
| 106 | 137 | ||
| 107 | const VAddr return_ptr{context.AddHeap(0u)}; | 138 | // Function call prototype: |
| 139 | // u64 Control(s32* ret, JITConfiguration* cfg, u64 cmd, u8* input_buf, size_t input_size, | ||
| 140 | // u8* output_buf, size_t output_size); | ||
| 141 | // | ||
| 142 | // This function is used to set up the state of the plugin before code generation, generally | ||
| 143 | // passing objects like pointers to VM state from the game. It is usually called once. | ||
| 144 | |||
| 145 | const VAddr ret_ptr{context.AddHeap(0u)}; | ||
| 108 | const VAddr configuration_ptr{context.AddHeap(configuration)}; | 146 | const VAddr configuration_ptr{context.AddHeap(configuration)}; |
| 109 | const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; | 147 | const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; |
| 110 | const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; | 148 | const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; |
| 111 | const u64 wrapper_value{ | 149 | |
| 112 | context.CallFunction(callbacks.Control, return_ptr, configuration_ptr, command, | 150 | const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, |
| 113 | input_ptr, input_buffer.size(), output_ptr, output_buffer.size())}; | 151 | command, input_ptr, input_buffer.size(), |
| 114 | const s32 return_value{context.GetHeap<s32>(return_ptr)}; | 152 | output_ptr, output_buffer.size())}; |
| 153 | |||
| 154 | const s32 return_value{context.GetHeap<s32>(ret_ptr)}; | ||
| 115 | 155 | ||
| 116 | if (wrapper_value == 0 && return_value == 0) { | 156 | if (wrapper_value == 0 && return_value == 0) { |
| 157 | // Write back to the IPC output buffer, if provided | ||
| 117 | if (ctx.CanWriteBuffer()) { | 158 | if (ctx.CanWriteBuffer()) { |
| 118 | context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); | 159 | context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); |
| 119 | ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); | 160 | ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); |
| 120 | } | 161 | } |
| 162 | |||
| 121 | IPC::ResponseBuilder rb{ctx, 3}; | 163 | IPC::ResponseBuilder rb{ctx, 3}; |
| 122 | rb.Push(ResultSuccess); | 164 | rb.Push(ResultSuccess); |
| 123 | rb.Push(return_value); | 165 | rb.Push(return_value); |
| @@ -129,8 +171,13 @@ public: | |||
| 129 | } | 171 | } |
| 130 | 172 | ||
| 131 | void LoadPlugin(Kernel::HLERequestContext& ctx) { | 173 | void LoadPlugin(Kernel::HLERequestContext& ctx) { |
| 174 | LOG_DEBUG(Service_JIT, "called"); | ||
| 175 | |||
| 132 | IPC::RequestParser rp{ctx}; | 176 | IPC::RequestParser rp{ctx}; |
| 133 | const auto tmem_size{rp.PopRaw<u64>()}; | 177 | const auto tmem_size{rp.PopRaw<u64>()}; |
| 178 | const auto tmem_handle{ctx.GetCopyHandle(0)}; | ||
| 179 | const auto nro_plugin{ctx.ReadBuffer(1)}; | ||
| 180 | |||
| 134 | if (tmem_size == 0) { | 181 | if (tmem_size == 0) { |
| 135 | LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory"); | 182 | LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory"); |
| 136 | IPC::ResponseBuilder rb{ctx, 2}; | 183 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -138,9 +185,7 @@ public: | |||
| 138 | return; | 185 | return; |
| 139 | } | 186 | } |
| 140 | 187 | ||
| 141 | const auto tmem_handle{ctx.GetCopyHandle(0)}; | 188 | auto tmem{process->GetHandleTable().GetObject<Kernel::KTransferMemory>(tmem_handle)}; |
| 142 | auto tmem{system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 143 | tmem_handle)}; | ||
| 144 | if (tmem.IsNull()) { | 189 | if (tmem.IsNull()) { |
| 145 | LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle"); | 190 | LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle"); |
| 146 | IPC::ResponseBuilder rb{ctx, 2}; | 191 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -148,24 +193,24 @@ public: | |||
| 148 | return; | 193 | return; |
| 149 | } | 194 | } |
| 150 | 195 | ||
| 151 | configuration.work_memory.offset = tmem->GetSourceAddress(); | 196 | // Set up the configuration with the required TransferMemory address |
| 152 | configuration.work_memory.size = tmem_size; | 197 | configuration.transfer_memory.offset = tmem->GetSourceAddress(); |
| 198 | configuration.transfer_memory.size = tmem_size; | ||
| 153 | 199 | ||
| 154 | const auto nro_plugin{ctx.ReadBuffer(1)}; | 200 | // Gather up all the callbacks from the loaded plugin |
| 155 | auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)}; | 201 | auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)}; |
| 156 | const auto GetSymbol{[&](std::string name) { return symbols[name].first; }}; | 202 | const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }}; |
| 157 | 203 | ||
| 158 | callbacks = | 204 | callbacks.rtld_fini = GetSymbol("_fini"); |
| 159 | GuestCallbacks{.rtld_fini = GetSymbol("_fini"), | 205 | callbacks.rtld_init = GetSymbol("_init"); |
| 160 | .rtld_init = GetSymbol("_init"), | 206 | callbacks.Control = GetSymbol("nnjitpluginControl"); |
| 161 | .Control = GetSymbol("nnjitpluginControl"), | 207 | callbacks.ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols"); |
| 162 | .ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols"), | 208 | callbacks.SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics"); |
| 163 | .SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics"), | 209 | callbacks.Configure = GetSymbol("nnjitpluginConfigure"); |
| 164 | .Configure = GetSymbol("nnjitpluginConfigure"), | 210 | callbacks.GenerateCode = GetSymbol("nnjitpluginGenerateCode"); |
| 165 | .GenerateCode = GetSymbol("nnjitpluginGenerateCode"), | 211 | callbacks.GetVersion = GetSymbol("nnjitpluginGetVersion"); |
| 166 | .GetVersion = GetSymbol("nnjitpluginGetVersion"), | 212 | callbacks.OnPrepared = GetSymbol("nnjitpluginOnPrepared"); |
| 167 | .Keeper = GetSymbol("nnjitpluginKeeper"), | 213 | callbacks.Keeper = GetSymbol("nnjitpluginKeeper"); |
| 168 | .OnPrepared = GetSymbol("nnjitpluginOnPrepared")}; | ||
| 169 | 214 | ||
| 170 | if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || | 215 | if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || |
| 171 | callbacks.OnPrepared == 0) { | 216 | callbacks.OnPrepared == 0) { |
| @@ -186,12 +231,16 @@ public: | |||
| 186 | configuration.sys_ro_memory.size); | 231 | configuration.sys_ro_memory.size); |
| 187 | context.MapProcessMemory(configuration.sys_rx_memory.offset, | 232 | context.MapProcessMemory(configuration.sys_rx_memory.offset, |
| 188 | configuration.sys_rx_memory.size); | 233 | configuration.sys_rx_memory.size); |
| 189 | context.MapProcessMemory(configuration.work_memory.offset, configuration.work_memory.size); | 234 | context.MapProcessMemory(configuration.transfer_memory.offset, |
| 235 | configuration.transfer_memory.size); | ||
| 190 | 236 | ||
| 237 | // Run ELF constructors, if needed | ||
| 191 | if (callbacks.rtld_init != 0) { | 238 | if (callbacks.rtld_init != 0) { |
| 192 | context.CallFunction(callbacks.rtld_init); | 239 | context.CallFunction(callbacks.rtld_init); |
| 193 | } | 240 | } |
| 194 | 241 | ||
| 242 | // Function prototype: | ||
| 243 | // u64 GetVersion(); | ||
| 195 | const auto version{context.CallFunction(callbacks.GetVersion)}; | 244 | const auto version{context.CallFunction(callbacks.GetVersion)}; |
| 196 | if (version != 1) { | 245 | if (version != 1) { |
| 197 | LOG_ERROR(Service_JIT, "unknown plugin version {}", version); | 246 | LOG_ERROR(Service_JIT, "unknown plugin version {}", version); |
| @@ -200,16 +249,26 @@ public: | |||
| 200 | return; | 249 | return; |
| 201 | } | 250 | } |
| 202 | 251 | ||
| 252 | // Function prototype: | ||
| 253 | // void ResolveBasicSymbols(void (*resolver)(const char* name)); | ||
| 203 | const auto resolve{context.GetHelper("_resolve")}; | 254 | const auto resolve{context.GetHelper("_resolve")}; |
| 204 | if (callbacks.ResolveBasicSymbols != 0) { | 255 | if (callbacks.ResolveBasicSymbols != 0) { |
| 205 | context.CallFunction(callbacks.ResolveBasicSymbols, resolve); | 256 | context.CallFunction(callbacks.ResolveBasicSymbols, resolve); |
| 206 | } | 257 | } |
| 258 | |||
| 259 | // Function prototype: | ||
| 260 | // void SetupDiagnostics(u32 enabled, void (**resolver)(const char* name)); | ||
| 207 | const auto resolve_ptr{context.AddHeap(resolve)}; | 261 | const auto resolve_ptr{context.AddHeap(resolve)}; |
| 208 | if (callbacks.SetupDiagnostics != 0) { | 262 | if (callbacks.SetupDiagnostics != 0) { |
| 209 | context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr); | 263 | context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr); |
| 210 | } | 264 | } |
| 211 | 265 | ||
| 212 | context.CallFunction(callbacks.Configure, 0u); | 266 | // Function prototype: |
| 267 | // void Configure(u32* memory_flags); | ||
| 268 | context.CallFunction(callbacks.Configure, 0ull); | ||
| 269 | |||
| 270 | // Function prototype: | ||
| 271 | // void OnPrepared(JITConfiguration* cfg); | ||
| 213 | const auto configuration_ptr{context.AddHeap(configuration)}; | 272 | const auto configuration_ptr{context.AddHeap(configuration)}; |
| 214 | context.CallFunction(callbacks.OnPrepared, configuration_ptr); | 273 | context.CallFunction(callbacks.OnPrepared, configuration_ptr); |
| 215 | 274 | ||
| @@ -218,6 +277,8 @@ public: | |||
| 218 | } | 277 | } |
| 219 | 278 | ||
| 220 | void GetCodeAddress(Kernel::HLERequestContext& ctx) { | 279 | void GetCodeAddress(Kernel::HLERequestContext& ctx) { |
| 280 | LOG_DEBUG(Service_JIT, "called"); | ||
| 281 | |||
| 221 | IPC::ResponseBuilder rb{ctx, 6}; | 282 | IPC::ResponseBuilder rb{ctx, 6}; |
| 222 | rb.Push(ResultSuccess); | 283 | rb.Push(ResultSuccess); |
| 223 | rb.Push(configuration.user_rx_memory.offset); | 284 | rb.Push(configuration.user_rx_memory.offset); |
| @@ -243,11 +304,17 @@ private: | |||
| 243 | struct JITConfiguration { | 304 | struct JITConfiguration { |
| 244 | CodeRange user_rx_memory; | 305 | CodeRange user_rx_memory; |
| 245 | CodeRange user_ro_memory; | 306 | CodeRange user_ro_memory; |
| 246 | CodeRange work_memory; | 307 | CodeRange transfer_memory; |
| 247 | CodeRange sys_rx_memory; | 308 | CodeRange sys_rx_memory; |
| 248 | CodeRange sys_ro_memory; | 309 | CodeRange sys_ro_memory; |
| 249 | }; | 310 | }; |
| 250 | 311 | ||
| 312 | static CodeRange ClearSize(CodeRange in) { | ||
| 313 | in.size = 0; | ||
| 314 | return in; | ||
| 315 | } | ||
| 316 | |||
| 317 | Kernel::KScopedAutoObject<Kernel::KProcess> process; | ||
| 251 | GuestCallbacks callbacks; | 318 | GuestCallbacks callbacks; |
| 252 | JITConfiguration configuration; | 319 | JITConfiguration configuration; |
| 253 | JITContext context; | 320 | JITContext context; |
| @@ -275,8 +342,9 @@ public: | |||
| 275 | 342 | ||
| 276 | IPC::RequestParser rp{ctx}; | 343 | IPC::RequestParser rp{ctx}; |
| 277 | const auto parameters{rp.PopRaw<Parameters>()}; | 344 | const auto parameters{rp.PopRaw<Parameters>()}; |
| 278 | const auto executable_mem_handle{ctx.GetCopyHandle(1)}; | 345 | const auto process_handle{ctx.GetCopyHandle(0)}; |
| 279 | const auto readable_mem_handle{ctx.GetCopyHandle(2)}; | 346 | const auto rx_mem_handle{ctx.GetCopyHandle(1)}; |
| 347 | const auto ro_mem_handle{ctx.GetCopyHandle(2)}; | ||
| 280 | 348 | ||
| 281 | if (parameters.rx_size == 0 || parameters.ro_size == 0) { | 349 | if (parameters.rx_size == 0 || parameters.ro_size == 0) { |
| 282 | LOG_ERROR(Service_JIT, "attempted to init with empty code regions"); | 350 | LOG_ERROR(Service_JIT, "attempted to init with empty code regions"); |
| @@ -285,42 +353,47 @@ public: | |||
| 285 | return; | 353 | return; |
| 286 | } | 354 | } |
| 287 | 355 | ||
| 288 | // The copy handle at index 0 is the process handle, but handle tables are | 356 | // Fetch using the handle table for the current process here, |
| 289 | // per-process, so there is no point reading it here until we are multiprocess | 357 | // since we are not multiprocess yet. |
| 290 | const auto& process{*system.CurrentProcess()}; | 358 | const auto& handle_table{system.CurrentProcess()->GetHandleTable()}; |
| 359 | |||
| 360 | auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)}; | ||
| 361 | if (process.IsNull()) { | ||
| 362 | LOG_ERROR(Service_JIT, "process is null for handle=0x{:08X}", process_handle); | ||
| 363 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 364 | rb.Push(ResultUnknown); | ||
| 365 | return; | ||
| 366 | } | ||
| 291 | 367 | ||
| 292 | auto executable_mem{ | 368 | auto rx_mem{handle_table.GetObject<Kernel::KCodeMemory>(rx_mem_handle)}; |
| 293 | process.GetHandleTable().GetObject<Kernel::KCodeMemory>(executable_mem_handle)}; | 369 | if (rx_mem.IsNull()) { |
| 294 | if (executable_mem.IsNull()) { | 370 | LOG_ERROR(Service_JIT, "rx_mem is null for handle=0x{:08X}", rx_mem_handle); |
| 295 | LOG_ERROR(Service_JIT, "executable_mem is null for handle=0x{:08X}", | ||
| 296 | executable_mem_handle); | ||
| 297 | IPC::ResponseBuilder rb{ctx, 2}; | 371 | IPC::ResponseBuilder rb{ctx, 2}; |
| 298 | rb.Push(ResultUnknown); | 372 | rb.Push(ResultUnknown); |
| 299 | return; | 373 | return; |
| 300 | } | 374 | } |
| 301 | 375 | ||
| 302 | auto readable_mem{ | 376 | auto ro_mem{handle_table.GetObject<Kernel::KCodeMemory>(ro_mem_handle)}; |
| 303 | process.GetHandleTable().GetObject<Kernel::KCodeMemory>(readable_mem_handle)}; | 377 | if (ro_mem.IsNull()) { |
| 304 | if (readable_mem.IsNull()) { | 378 | LOG_ERROR(Service_JIT, "ro_mem is null for handle=0x{:08X}", ro_mem_handle); |
| 305 | LOG_ERROR(Service_JIT, "readable_mem is null for handle=0x{:08X}", readable_mem_handle); | ||
| 306 | IPC::ResponseBuilder rb{ctx, 2}; | 379 | IPC::ResponseBuilder rb{ctx, 2}; |
| 307 | rb.Push(ResultUnknown); | 380 | rb.Push(ResultUnknown); |
| 308 | return; | 381 | return; |
| 309 | } | 382 | } |
| 310 | 383 | ||
| 311 | const CodeRange user_rx{ | 384 | const CodeRange user_rx{ |
| 312 | .offset = executable_mem->GetSourceAddress(), | 385 | .offset = rx_mem->GetSourceAddress(), |
| 313 | .size = parameters.rx_size, | 386 | .size = parameters.rx_size, |
| 314 | }; | 387 | }; |
| 315 | 388 | ||
| 316 | const CodeRange user_ro{ | 389 | const CodeRange user_ro{ |
| 317 | .offset = readable_mem->GetSourceAddress(), | 390 | .offset = ro_mem->GetSourceAddress(), |
| 318 | .size = parameters.ro_size, | 391 | .size = parameters.ro_size, |
| 319 | }; | 392 | }; |
| 320 | 393 | ||
| 321 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 394 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 322 | rb.Push(ResultSuccess); | 395 | rb.Push(ResultSuccess); |
| 323 | rb.PushIpcInterface<IJitEnvironment>(system, user_rx, user_ro); | 396 | rb.PushIpcInterface<IJitEnvironment>(system, *process, user_rx, user_ro); |
| 324 | } | 397 | } |
| 325 | }; | 398 | }; |
| 326 | 399 | ||
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp index 17e58131c..19bd85b6c 100644 --- a/src/core/hle/service/jit/jit_context.cpp +++ b/src/core/hle/service/jit/jit_context.cpp | |||
| @@ -17,61 +17,15 @@ | |||
| 17 | 17 | ||
| 18 | namespace Service::JIT { | 18 | namespace Service::JIT { |
| 19 | 19 | ||
| 20 | constexpr std::array<u8, 4> STOP_ARM64 = { | 20 | constexpr std::array<u8, 8> SVC0_ARM64 = { |
| 21 | 0x01, 0x00, 0x00, 0xd4, // svc #0 | 21 | 0x01, 0x00, 0x00, 0xd4, // svc #0 |
| 22 | }; | ||
| 23 | |||
| 24 | constexpr std::array<u8, 8> RESOLVE_ARM64 = { | ||
| 25 | 0x21, 0x00, 0x00, 0xd4, // svc #1 | ||
| 26 | 0xc0, 0x03, 0x5f, 0xd6, // ret | 22 | 0xc0, 0x03, 0x5f, 0xd6, // ret |
| 27 | }; | 23 | }; |
| 28 | 24 | ||
| 29 | constexpr std::array<u8, 4> PANIC_ARM64 = { | 25 | constexpr std::array HELPER_FUNCTIONS{ |
| 30 | 0x41, 0x00, 0x00, 0xd4, // svc #2 | 26 | "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset", |
| 31 | }; | ||
| 32 | |||
| 33 | constexpr std::array<u8, 60> MEMMOVE_ARM64 = { | ||
| 34 | 0x1f, 0x00, 0x01, 0xeb, // cmp x0, x1 | ||
| 35 | 0x83, 0x01, 0x00, 0x54, // b.lo #+34 | ||
| 36 | 0x42, 0x04, 0x00, 0xd1, // sub x2, x2, 1 | ||
| 37 | 0x22, 0x01, 0xf8, 0xb7, // tbnz x2, #63, #+36 | ||
| 38 | 0x23, 0x68, 0x62, 0x38, // ldrb w3, [x1, x2] | ||
| 39 | 0x03, 0x68, 0x22, 0x38, // strb w3, [x0, x2] | ||
| 40 | 0xfc, 0xff, 0xff, 0x17, // b #-16 | ||
| 41 | 0x24, 0x68, 0x63, 0x38, // ldrb w4, [x1, x3] | ||
| 42 | 0x04, 0x68, 0x23, 0x38, // strb w4, [x0, x3] | ||
| 43 | 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 | ||
| 44 | 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 | ||
| 45 | 0x8b, 0xff, 0xff, 0x54, // b.lt #-16 | ||
| 46 | 0xc0, 0x03, 0x5f, 0xd6, // ret | ||
| 47 | 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 | ||
| 48 | 0xfc, 0xff, 0xff, 0x17, // b #-16 | ||
| 49 | }; | 27 | }; |
| 50 | 28 | ||
| 51 | constexpr std::array<u8, 28> MEMSET_ARM64 = { | ||
| 52 | 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 | ||
| 53 | 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 | ||
| 54 | 0x4b, 0x00, 0x00, 0x54, // b.lt #+8 | ||
| 55 | 0xc0, 0x03, 0x5f, 0xd6, // ret | ||
| 56 | 0x01, 0x68, 0x23, 0x38, // strb w1, [x0, x3] | ||
| 57 | 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 | ||
| 58 | 0xfb, 0xff, 0xff, 0x17, // b #-20 | ||
| 59 | }; | ||
| 60 | |||
| 61 | struct HelperFunction { | ||
| 62 | const char* name; | ||
| 63 | const std::span<const u8> data; | ||
| 64 | }; | ||
| 65 | |||
| 66 | constexpr std::array<HelperFunction, 6> HELPER_FUNCTIONS{{ | ||
| 67 | {"_stop", STOP_ARM64}, | ||
| 68 | {"_resolve", RESOLVE_ARM64}, | ||
| 69 | {"_panic", PANIC_ARM64}, | ||
| 70 | {"memcpy", MEMMOVE_ARM64}, | ||
| 71 | {"memmove", MEMMOVE_ARM64}, | ||
| 72 | {"memset", MEMSET_ARM64}, | ||
| 73 | }}; | ||
| 74 | |||
| 75 | struct Elf64_Dyn { | 29 | struct Elf64_Dyn { |
| 76 | u64 d_tag; | 30 | u64 d_tag; |
| 77 | u64 d_un; | 31 | u64 d_un; |
| @@ -224,17 +178,24 @@ public: | |||
| 224 | InsertHelperFunctions(); | 178 | InsertHelperFunctions(); |
| 225 | InsertStack(); | 179 | InsertStack(); |
| 226 | return true; | 180 | return true; |
| 227 | } else { | ||
| 228 | return false; | ||
| 229 | } | 181 | } |
| 182 | |||
| 183 | return false; | ||
| 230 | } | 184 | } |
| 231 | 185 | ||
| 232 | bool FixupRelocations() { | 186 | bool FixupRelocations() { |
| 187 | // The loaded NRO file has ELF relocations that must be processed before it can run. | ||
| 188 | // Normally this would be processed by RTLD, but in HLE context, we don't have | ||
| 189 | // the linker available, so we have to do it ourselves. | ||
| 190 | |||
| 233 | const VAddr mod_offset{callbacks->MemoryRead32(4)}; | 191 | const VAddr mod_offset{callbacks->MemoryRead32(4)}; |
| 234 | if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { | 192 | if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { |
| 235 | return false; | 193 | return false; |
| 236 | } | 194 | } |
| 237 | 195 | ||
| 196 | // For more info about dynamic entries, see the ELF ABI specification: | ||
| 197 | // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html | ||
| 198 | // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html | ||
| 238 | VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; | 199 | VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; |
| 239 | VAddr rela_dyn = 0; | 200 | VAddr rela_dyn = 0; |
| 240 | size_t num_rela = 0; | 201 | size_t num_rela = 0; |
| @@ -266,13 +227,15 @@ public: | |||
| 266 | } | 227 | } |
| 267 | 228 | ||
| 268 | void InsertHelperFunctions() { | 229 | void InsertHelperFunctions() { |
| 269 | for (const auto& [name, contents] : HELPER_FUNCTIONS) { | 230 | for (const auto& name : HELPER_FUNCTIONS) { |
| 270 | helpers[name] = local_memory.size(); | 231 | helpers[name] = local_memory.size(); |
| 271 | local_memory.insert(local_memory.end(), contents.begin(), contents.end()); | 232 | local_memory.insert(local_memory.end(), SVC0_ARM64.begin(), SVC0_ARM64.end()); |
| 272 | } | 233 | } |
| 273 | } | 234 | } |
| 274 | 235 | ||
| 275 | void InsertStack() { | 236 | void InsertStack() { |
| 237 | // Allocate enough space to avoid any reasonable risk of | ||
| 238 | // overflowing the stack during plugin execution | ||
| 276 | const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - | 239 | const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - |
| 277 | local_memory.size()}; | 240 | local_memory.size()}; |
| 278 | local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); | 241 | local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); |
| @@ -292,9 +255,21 @@ public: | |||
| 292 | } | 255 | } |
| 293 | 256 | ||
| 294 | void SetupArguments() { | 257 | void SetupArguments() { |
| 258 | // The first 8 integer registers are used for the first 8 integer | ||
| 259 | // arguments. Floating-point arguments are not handled at this time. | ||
| 260 | // | ||
| 261 | // If a function takes more than 8 arguments, then stack space is reserved | ||
| 262 | // for the remaining arguments, and the remaining arguments are inserted in | ||
| 263 | // ascending memory order, each argument aligned to an 8-byte boundary. The | ||
| 264 | // stack pointer must remain aligned to 16 bytes. | ||
| 265 | // | ||
| 266 | // For more info, see the AArch64 ABI PCS: | ||
| 267 | // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst | ||
| 268 | |||
| 295 | for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { | 269 | for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { |
| 296 | jit->SetRegister(i, argument_stack[i]); | 270 | jit->SetRegister(i, argument_stack[i]); |
| 297 | } | 271 | } |
| 272 | |||
| 298 | if (argument_stack.size() > 8) { | 273 | if (argument_stack.size() > 8) { |
| 299 | const VAddr new_sp = Common::AlignDown( | 274 | const VAddr new_sp = Common::AlignDown( |
| 300 | top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); | 275 | top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); |
| @@ -303,6 +278,8 @@ public: | |||
| 303 | } | 278 | } |
| 304 | jit->SetSP(new_sp); | 279 | jit->SetSP(new_sp); |
| 305 | } | 280 | } |
| 281 | |||
| 282 | // Reset the call state for the next invocation | ||
| 306 | argument_stack.clear(); | 283 | argument_stack.clear(); |
| 307 | heap_pointer = top_of_stack; | 284 | heap_pointer = top_of_stack; |
| 308 | } | 285 | } |
| @@ -322,11 +299,16 @@ public: | |||
| 322 | } | 299 | } |
| 323 | 300 | ||
| 324 | VAddr AddHeap(const void* data, size_t size) { | 301 | VAddr AddHeap(const void* data, size_t size) { |
| 302 | // Require all heap data types to have the same alignment as the | ||
| 303 | // stack pointer, for compatibility | ||
| 325 | const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; | 304 | const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; |
| 305 | |||
| 306 | // Make additional memory space if required | ||
| 326 | if (heap_pointer + num_bytes > local_memory.size()) { | 307 | if (heap_pointer + num_bytes > local_memory.size()) { |
| 327 | local_memory.insert(local_memory.end(), | 308 | local_memory.insert(local_memory.end(), |
| 328 | (heap_pointer + num_bytes) - local_memory.size(), 0); | 309 | (heap_pointer + num_bytes) - local_memory.size(), 0); |
| 329 | } | 310 | } |
| 311 | |||
| 330 | const VAddr location{heap_pointer}; | 312 | const VAddr location{heap_pointer}; |
| 331 | std::memcpy(local_memory.data() + location, data, size); | 313 | std::memcpy(local_memory.data() + location, data, size); |
| 332 | heap_pointer += num_bytes; | 314 | heap_pointer += num_bytes; |
| @@ -350,30 +332,67 @@ public: | |||
| 350 | }; | 332 | }; |
| 351 | 333 | ||
| 352 | void DynarmicCallbacks64::CallSVC(u32 swi) { | 334 | void DynarmicCallbacks64::CallSVC(u32 swi) { |
| 353 | switch (swi) { | 335 | // Service calls are used to implement helper functionality. |
| 354 | case 0: | 336 | // |
| 337 | // The most important of these is the _stop helper, which transfers control | ||
| 338 | // from the plugin back to HLE context to return a value. However, a few more | ||
| 339 | // are also implemented to reduce the need for direct ARM implementations of | ||
| 340 | // basic functionality, like memory operations. | ||
| 341 | // | ||
| 342 | // When we receive a helper request, the swi number will be zero, and the call | ||
| 343 | // will have originated from an address we know is a helper function. Otherwise, | ||
| 344 | // the plugin may be trying to issue a service call, which we shouldn't handle. | ||
| 345 | |||
| 346 | if (swi != 0) { | ||
| 347 | LOG_CRITICAL(Service_JIT, "plugin issued unknown service call {}", swi); | ||
| 355 | parent.jit->HaltExecution(); | 348 | parent.jit->HaltExecution(); |
| 356 | break; | 349 | return; |
| 350 | } | ||
| 351 | |||
| 352 | u64 pc{parent.jit->GetPC() - 4}; | ||
| 353 | auto& helpers{parent.helpers}; | ||
| 354 | |||
| 355 | if (pc == helpers["memcpy"] || pc == helpers["memmove"]) { | ||
| 356 | const VAddr dest{parent.jit->GetRegister(0)}; | ||
| 357 | const VAddr src{parent.jit->GetRegister(1)}; | ||
| 358 | const size_t n{parent.jit->GetRegister(2)}; | ||
| 359 | |||
| 360 | if (dest < src) { | ||
| 361 | for (size_t i = 0; i < n; i++) { | ||
| 362 | MemoryWrite8(dest + i, MemoryRead8(src + i)); | ||
| 363 | } | ||
| 364 | } else { | ||
| 365 | for (size_t i = n; i > 0; i--) { | ||
| 366 | MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1)); | ||
| 367 | } | ||
| 368 | } | ||
| 369 | } else if (pc == helpers["memset"]) { | ||
| 370 | const VAddr dest{parent.jit->GetRegister(0)}; | ||
| 371 | const u64 c{parent.jit->GetRegister(1)}; | ||
| 372 | const size_t n{parent.jit->GetRegister(2)}; | ||
| 357 | 373 | ||
| 358 | case 1: { | 374 | for (size_t i = 0; i < n; i++) { |
| 375 | MemoryWrite8(dest + i, static_cast<u8>(c)); | ||
| 376 | } | ||
| 377 | } else if (pc == helpers["_resolve"]) { | ||
| 359 | // X0 contains a char* for a symbol to resolve | 378 | // X0 contains a char* for a symbol to resolve |
| 360 | std::string name{MemoryReadCString(parent.jit->GetRegister(0))}; | 379 | const auto name{MemoryReadCString(parent.jit->GetRegister(0))}; |
| 361 | const auto helper{parent.helpers[name]}; | 380 | const auto helper{helpers[name]}; |
| 362 | 381 | ||
| 363 | if (helper != 0) { | 382 | if (helper != 0) { |
| 364 | parent.jit->SetRegister(0, helper); | 383 | parent.jit->SetRegister(0, helper); |
| 365 | } else { | 384 | } else { |
| 366 | LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); | 385 | LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); |
| 367 | parent.jit->SetRegister(0, parent.helpers["_panic"]); | 386 | parent.jit->SetRegister(0, helpers["_panic"]); |
| 368 | } | 387 | } |
| 369 | break; | 388 | } else if (pc == helpers["_stop"]) { |
| 370 | } | 389 | parent.jit->HaltExecution(); |
| 371 | 390 | } else if (pc == helpers["_panic"]) { | |
| 372 | case 2: | ||
| 373 | default: | ||
| 374 | LOG_CRITICAL(Service_JIT, "plugin panicked!"); | 391 | LOG_CRITICAL(Service_JIT, "plugin panicked!"); |
| 375 | parent.jit->HaltExecution(); | 392 | parent.jit->HaltExecution(); |
| 376 | break; | 393 | } else { |
| 394 | LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address 0x{:x}", pc); | ||
| 395 | parent.jit->HaltExecution(); | ||
| 377 | } | 396 | } |
| 378 | } | 397 | } |
| 379 | 398 | ||
diff --git a/src/core/hle/service/jit/jit_context.h b/src/core/hle/service/jit/jit_context.h index 3cb935e3c..f17fc5e24 100644 --- a/src/core/hle/service/jit/jit_context.h +++ b/src/core/hle/service/jit/jit_context.h | |||
| @@ -28,6 +28,7 @@ public: | |||
| 28 | template <typename T, typename... Ts> | 28 | template <typename T, typename... Ts> |
| 29 | u64 CallFunction(VAddr func, T argument, Ts... rest) { | 29 | u64 CallFunction(VAddr func, T argument, Ts... rest) { |
| 30 | static_assert(std::is_trivially_copyable_v<T>); | 30 | static_assert(std::is_trivially_copyable_v<T>); |
| 31 | static_assert(!std::is_floating_point_v<T>); | ||
| 31 | PushArgument(&argument, sizeof(argument)); | 32 | PushArgument(&argument, sizeof(argument)); |
| 32 | 33 | ||
| 33 | if constexpr (sizeof...(rest) > 0) { | 34 | if constexpr (sizeof...(rest) > 0) { |