зеркало из
1
0
Форкнуть 0
MSIX-PackageSupportFramework/PsfRuntime/readme.md

4.9 KiB

PSF Runtime

The PSF Runtime serves several purposes. The first is that it is responsible for detouring CreateProcess to ensure that any child process will also get the PSF Runtime injected into it. Additionally, it is responsible for loading and parsing the config.json DOM as well as loading any configured fixup dlls for the current executable. Finally, it exposes a set of utility functions collectively referred to as the "PSF Framework" for use by the individual fixup dlls. This includes helpers for interop with the Detours library, functions for querying information about the current package/app id, and a set of functions for querying information from the config.json DOM. See psf_runtime.h for a more complete idea of this API surface as well as psf_config.h for an idea of how the JSON data is exposed.

Fixup Loading

As mentioned above, one of the major responsibilities that the PSF Runtime has is loading the fixups that are configured for the current executable. E.g. for a configuration that looks something like:

{
    "processes": [
        {
            "executable": "ContosoApp",
            "fixups": [
                {
                    "dll": "ContosoFixup.dll"
                }
            ]
        }
    ]
}

The PSF Runtime will load ContosoFixup.dll if the current executable name matches the regular expression "ContosoApp" (i.e. it must be an exact match for this example). Note that this pattern is missing the ".exe" suffix. This is done because periods in regular expressions must be escaped, which in JSON requires a double back-slash to do so (i.e. "name\\.exe"), which complicates the configuration and harms readability.

When enumerating the list of fixup dll names, PSF Runtime will make two attempts to load the dll, each made relative to the package root:

  1. First using the name exactly as it is given (e.g. "ContosoFixup.dll" in the example above)
  2. If that fails, then it appends the current architecture bitness (32 or 64) to the end of the dll name (e.g. "ContosoFixup64.dll" if the current process is a 64-bit process) and attempts to load that

This is done to support applications that contain both 32 and 64-bit executables, but need the same fixups applied to both. This is why all of the inbox fixups produce binaries named _____32.dll and _____64.dll by default. This naming convention also carries over to executables to prevent naming conflicts.

NOTE: This naming scheme and functionality is taken from the Detours library, which has similar load/fallback logic, to be consistent

Assuming the dll loads, the PSF Runtime calls GetProcAddress, expecting the two exports PSFInitialize and PSFUninitialize, treating failure the same as if the dll failed to load. Both functions are assumed to have the signature:

int (__stdcall *)() noexcept`

The PSF Runtime then calls PSFInitialize within a Detours transaction, failing out if the return value is non-zero (i.e. not ERROR_SUCCESS). Within the execution of PSFInitialize, the fixup dll is free to call PSFRegister, which in turn calls DetourAttach. Calling PSFRegister at any other time will fail. When the initialize procedure returns, the transaction is completed, committing any function detours set up by the fixup dll. When the PSF Runtime dll is being unloaded, it will enumerate the set of loaded fixups in reverse order, calling PSFUninitialize. At this point in time, the fixup dll is expected to call PSFUnregister for every prior call it made to PSFRegister (which calls DetourDetach) before getting unloaded to avoid later attempts to call back into an unloaded dll.

IMPORTANT: The exported names must exactly match PSFInitialize and PSFUninitialize. This isn't automatic when using __declspec(dllexport) due to the "mangling" performed for 32-bit binaries

TIP: In most cases you can leverage the PSF_DEFINE_EXPORTS macro to define/export these functions for you with the correct names. See here for more information

Runtime Requirements

As a part of its initialization, the PSF Runtime queries information about its environment that it then caches for later use. A few examples include parsing the config.json, caching the path to the package root, and caching the package name, among a couple other things. If any of these steps fail, e.g. because something is not present/cannot be found or any other failure, then the PSF Runtime dll will fail to load, which likely means that the process fails to start. Note that this implies the requirement that the application be running with package identity. There have been past conversations on adding support for a "debug" mode that works around this restriction (e.g. by using a fake package name, executable directory as the package root, etc.), but its benefit is questionable and has not yet been implemented.