Difference between revisions of "JIT services"

From Nintendo Switch Brew
Jump to navigation Jump to search
Line 22: Line 22:
 
== CreateJitEnvironment ==
 
== CreateJitEnvironment ==
 
Takes two input u64s, 3 input handles, returns an [[#IJitEnvironment]].
 
Takes two input u64s, 3 input handles, returns an [[#IJitEnvironment]].
 +
 +
This essentially does state/object init and maps the CodeMemory regions in the user-process.
  
 
= IJitEnvironment =
 
= IJitEnvironment =

Revision as of 04:19, 16 April 2020

JIT is a sysmodule for run-time code generation (allowing for overlapping R-X and RW- views of memory). This was added to retail with [10.0.0+]. This was also supported in sdknso for a number of versions prior.

nnMain just initializes ro:1, then starts hosting the service from the main-thread with max_sessions=1 (threads are not created for service-hosting).

This is intended to only be used by Applications. The service-init in sdknso just uses PrepareForJit at the start, then gets the service.

sdknso CreateJitEnvironment implements the remaining initialization. After some validation, this uses svcCreateCodeMemory (can be called twice). Then #CreateJitEnvironment is used. TransferMemory with an user-specified buffer is created with permissions=None, which is then used with #LoadPlugin. When successful, this lastly uses #GetCodeAddress.

This loads the user-specified NRO into sysmodule-context ("DllPlugin"), and calls various symbols from that NRO. It seems the code writing (in cmd GenerateCode) is done via symbol-calling, allowing the NRO to handle input_buffer->code translation+writing.

jit:u

This is "nn::jitsrv::IJitService".

Cmd Name
0 #CreateJitEnvironment

CreateJitEnvironment

Takes two input u64s, 3 input handles, returns an #IJitEnvironment.

This essentially does state/object init and maps the CodeMemory regions in the user-process.

IJitEnvironment

This is "nn::jitsrv::IJitEnvironment".

Cmd Name
0 #GenerateCode
1 #Control
1000 #LoadPlugin
1001 #GetCodeAddress

GenerateCode

Takes an u32, an u64, a #CodeRange, a #CodeRange, a #Struct32, a type-0x5 input buffer, a type-0x6 output buffer, and returns an output s32, a #CodeRange, a #CodeRange.

Control

Takes an input u64, a type-0x5 input buffer, a type-0x6 output buffer, and returns an output s32.

LoadPlugin

Takes an input u64 tmem_size, a TransferMemory handle, two type-0x5 input buffers, no output.

The first buffer contains the NRR, the second buffer contains the NRO.

The tmem is temporarily mapped & cleared, when any errors this will also be done again. This always only mapped temporarily. This is referred to as "WorkMemory".

The input NRR is used with RegisterModuleInfo2, then the NRO is used with LoadModule (these are copied into another buffer with the required alignment). Afterwards, various symbol lookup is done with the loaded module:

  • "nnjitpluginGetVersion", error is handled on failure. This is called with no args, if the u32 output is >1 an error is thrown.
  • "nnjitpluginResolveBasicSymbols", this is optional. When successful and the funcptr is valid, this is called with x0 = {funcptr which can be called by the plugin for symbol-lookup. funcptr x0 = symbol_str*, ret = symbol_funcptr - this internally calls "nn::ro::LookupSymbol"}.
  • "nnjitpluginSetupDiagnostics", this is optional. When successful and the funcptr is valid, this is called with w0=1 and x1 = {ptr to a funcptr on stack, the func for this is a duplicate of the one referenced above}.
  • "nnjitpluginConfigure", error is handled on failure. When GetDebugModeFlag returns true, the symbol funcptr is called with x0 = {ptr where 2 output u32s are located}, and then the two output u32s are loaded (that data on stack is cleared prior to calling the funcptr). Otherwise when false, it's called with x0=0 and the fields which would contain the output u32s are cleared to 0. These fields are "nn::jit::MemorySecurityMode".
  • {calls a vtable funcptr, and handles error on failure}
  • TransferMemory init is done here. An ASLR'd address for the TransferMemory mapped-address is determined, which will then be reused for all later mappings.
  • CodeMemory init func-calling is done for both regions, where w1={first output from "nnjitpluginConfigure" above}. Likewise with the TransferMemory, with w1={second output from "nnjitpluginConfigure" above}.
  • "nnjitpluginOnPrepared", error is handled on failure. Before/after calling this symbol funcptr, the TransferMemory is mapped/unmapped. The symbol funcptr is called with x0 = {ptr to struct on stack}. The struct has following structure: +0 = 0x20-bytes of data from state, +0x20 = TransferMemory map-addr, +0x28 = TransferMemory size, and +0x30 size 0x10-bytes is cleared.
  • Then this does cleanup and returns.

GetCodeAddress

No input, returns two output u64s which are loaded from state.

CodeRange

This is "nn::jit::CodeRange". This is a 0x10-byte struct. This is 8-byte aligned.

Struct32

This is "nn::jitsrv::Struct32". This is a 0x20-byte struct. This is 8-byte aligned.