Shared Source Common Language Infrastructure Platform Adaptation Layer
Specification
Contents
SSCLI 2.0 Notes
While the source code to the PAL has been updated for the SSCLI 2.0 release, this particular document has not. This document has been left for reference. Overview
This specification and the C header file (%ROTOR_DIR%\pal\inc\rotor_pal.h) together with the relevant portions of the
Microsoft® MSDN online documentation (msdn.microsoft.com),
form the specification for the Shared Source Common Language Infrastructure (SSCLI) Platform Adaptation
Layer (PAL). The PAL has been specified such that correct implementation will
enable the portable subset of the .NET Framework to be run on a variety of non-Microsoft® Windows® operating systems.
Note: The PAL alone is not sufficient to accomplish a port of the SSCLI
implementation - the recompilation of the portable subset and the
construction of an appropriate just-in-time (JIT) compiler are required as well.
The specification consists of the following sections:
- Top-Level Requirements - specifications affecting the entire PAL.
- Assumptions - a list of clarifying assumptions about this specification.
- PAL specific APIs - a list of APIs that are callable by clients of the PAL to perform required or optional PAL services.
- API specifications by importer - a series of sections in which the APIs are specified and organized by
the major functional unit that requires them. Each API is listed in only one section with a specification that spans the requirements of the whole system.
- Implementation hints - a section outlining suggestions for how to implement some of the more
complex parts of the PAL.
- General issues - a section containing items that have yet to be fully
specified and that will be addressed in future versions of this specification.
Host Computer and Operating System Requirements
This section describes the features that the underlying platform is expected to
support and programmatically expose to the PAL.
Host Computer
The current PAL implementation requires a 32-bit architecture.
Host Operating System
The following are requirements for any operating system on which you wish to
host the PAL.
File System Requirements
- Must support a hierarchical directory tree (read-only at startup, but read-write to run non-trivial applications)
- Must support enumeration of files within a directory
- Can support files as large as 2^64 - 1 bytes
Memory Requirements
- Must have memory-mapped files
- Must support shared-libraries, including dynamic load, unload, and symbol lookup
- Must have 16 megabytes (MB) of memory commit available for each SSCLI process
- Address 0 must be reserved and must trigger an exception
- Must support shared-memory between processes, with no explicit synchronization or update. Writes must be automatically visible to all other processes that share the memory.
- Must support runtime code-generation. Note that the FlushInstructionCache() API will be called to notify the PAL after the code has been generated/modified and before executing it.
- Must have one fixed page size for the entire address space.
Threading Requirements
- Must support pre-emptive multi-threading, where threads share one common address space.
- Must have a mechanism for storing and fetching per-thread data.
- Must support large stacks - 256k or larger per thread.
- Must support a fixed number of processors - SSCLI queries the number of processors at startup and self-tunes for
single processor vs. multiprocessor, including making assumptions about atomicity of memory accesses.
Miscellaneous Operating System Requirements
Furthermore, the host operating system must support the following:
- UNIX system-based style signal() handlers, or an equivalent mechanism for catching software errors such as accessing memory at an invalid address. The mechanism must be able to report back the CPU's registers at the time of the fault.
- All code is expected to run within a usermode process, and within the security context of that process.
- BSD UNIX system-style network sockets, to support managed System.Net.Sockets.
- Optional TCL/TK implementation, to support the managed platform
independent user interface (PIGUI).
Compilation Environment
The compilation environment requires:
- Assumes the following types in C/C++:
- sizeof(char)==1
- sizeof(short)==2
- sizeof(int)==4
- sizeof(long)==4
- sizeof(void*)==4
- 4-byte and 8-byte IEEE real data types and arithmetic available in the C/C++ compiler
- 64-bit integer data type supporting 64-bit arithmetic
Basic Requirements
The following are basic requirements of the PAL:
- The PAL binary is a shared library named librotor_pal.so for most UNIX system-based platforms, librotor_pal.dylib on Mac OS X, and rotor_pal.dll
on the Windows platform.
- The PAL uses dynamic linking to bind to system-provided libraries such as
the C runtime.
- The PAL may not spawn child processes, except on behalf of the PAL client. It
may not spawn worker processes or daemon processes.
- Once built, the PAL must not require any installation or configuration before it can be run.
- Do not copy source code or headers from other sources into the PAL implementation.
Copied code might have undesirable licensing issues.
- The PAL has a standalone test suite which validates that the implementation functions as expected, in tests/palsuite
- The PAL should include API tracing in the debug build. API trace logs are an invaluable debugging tool
for understanding bugs in both the CLI implementation and the PAL.
- Source location. The PAL will be rooted under /sscli (the root directory
of the distribution) as follows:
- .../sscli/pal/
- inc/rotor_pal.h - the PAL public header file
- unix/... - UNIX system-based PAL implementation
- win32/... - Windows PAL implementation
- ... - other future platform PAL implementations
It is expected that the PAL implementation in the /unix directory will be ported to various
UNIX systems by using #ifdef switches added to the existing UNIX system-based code. The PAL sources for
UNIX system-based platforms should not be under a unix/freeBSD/... directory or
other directory named for a specific UNIX system-based platform implementation. All file and directory names must be lowercase.
Assumptions and Format
In constructing this specification, the following assumptions and formatting
styles were used:
- Unless otherwise stated, the detailed documentation of each API listed should be taken from the MSDN CDs. The October 2001 edition is the base line for the reference.
- For each API listed below, the descriptive material should be interpreted as a delta (either additive, subtractive or modification) from the baseline outline in the previous line item. Unless specifically stated otherwise, the API descriptions below do not replace the base specification outlined in the previous line item.
- The reference implementation for the PAL is Windows XP Professional.
- In instances where the MSDN documentation differs from the Windows XP Professional behavior, the Windows XP Professional behavior should be considered correct, and the MSDN documentation considered incorrect.
- Each API has the following form:
- API Name in Bold.
- [Optional delta from the MSDN documentation.]
- Lines terminating in a hyphen indicate that there was no noted change from
the MSDN documentation for that item; it is merely listed for completeness.
PAL-specific Entry Points
PAL_Initialize
The PAL_Initialize function is to be called by all clients of the PAL, to allow the PAL to perform required initialization. It returns 0 on success and a nonzero
error value on failure. In the failure case, the PAL should display a meaningful error message if possible, since the host application will be unable to call any PAL APIs to print messages. The PAL must support the
exit() and ExitProcess() functions even if PAL_Initialize returns a failure code.
PAL_Initialize may be called multiple times if multiple PAL clients are loaded into the same process. The PAL should keep an internal count of the number of
PAL_Initialize calls.
The caller must pass its argument list (argc/argv), in order to support GetCommandLine on systems
that do not otherwise have a system call to fetch back the command-line arguments. Calls after the first should ignore argc/argv.
On return, the floating-point environment must be set up to be compatible with the default
Microsoft Win32® environment:
- Floating-point exceptions masked.
- Round to nearest.
- 53-bit precision.
All new threads created using CreateThread() must also have the floating-point environment initialized to these settings.
PAL_Terminate
PAL clients must call PAL_Terminate if they have previously called PAL_Initialize and it has succeeded. After this call, if the count of outstanding PAL clients drops to 0, then the only PAL APIs that must continue to function are
exit() and ExitProcess().
PAL_GetUserConfigurationDirectoryW
The PAL should return the directory in which the host should store
per-user configuration data. On Windows, this should be located under the
per-user directory returned from SHGetSpecialFolderPath() for CSIDL_PROFILE
(that is, the %userprofile% directory). On UNIX system-based platforms, it should be under
the user's home directory, and should be prefixed by "." so it is hidden.
To support multiple different SSCLI implementations running concurrently,
the user configuration directory should be unique per-PAL, as determined by
examining the full path to the PAL library. For more information see
Side-by-side support.The PAL must create the directory upon the first call to this API.
PAL_RegisterLibrary PAL_UnregisterLibrary
Called to register or unregister statically linked dynamic libraries. For details see the DllMain section.
- lpLibFileName - base name of the statically linked library (in other
words, "sscoree" with no extension or "lib" prefix).
PAL_GetPALDirectoryW
Returns the fully qualified directory name with which the PAL DLL was loaded,
including a trailing path separator. On failure, it returns FALSE and sets the
LastError value to a meaningful error code. For success, if the PAL was loaded
from, for example, "D:\rotor\v1.x86chk.rotor\rotor_pal.dll", the returned path
should be "D:\rotor\v1.x86chk.rotor\". This API will be used to form path names
that point back to the directory where the SSCLI binaries are installed.
PAL_Random
Source of a good randomness implementation. A correct implementation gathers randomness from multiple non deterministic sources like interrupt and network latencies.
The lpBuffer is filled with dwLen random bytes. Optionally, the caller can populate the buffer with data to use as an auxiliary random seed before calling the function.
- bStrong - set to TRUE if cryptographically strong randomness is required
(for example, for key generation).
On Windows, this function should be implemented using CryptGenRandom. On UNIX
system-based platforms, this function should be implemented by reading from /dev/random or /dev/urandom. If neither /dev/random or /dev/urandom is available, a stronger randomness function than the C runtime's
rand() function should be used.
PAL_get_stdout PAL_get_stdin PAL_get_stderr
These functions are called from the PAL-defined "stdout", "stdin", and "stderr" macros.
PAL_errno
Returns a pointer to the per-thread errno value.
PAL_LocalHandleToRemote
- hLocal - a HANDLE to a mutex, event, or process. Other types of HANDLE such as files are not required to be remotable.
Converts a HANDLE valid in the current process into a "cookie" that can be passed to another PAL-based process on the same
computer, and converted into a HANDLE valid in that process. Conversion from remote RHANDLE to local HANDLE is accomplished by calling
PAL_RemoteHandleToLocal.
The return value is INVALID_HANDLE_VALUE on failure, due to out-of-memory or invalid HANDLE value or type.
For more details on local and remote HANDLEs, see DuplicateHandle.
PAL_RemoteHandleToLocal
- hRemote - a remotable cookie created using PAL_LocalHandleToRemote in another process.
Creates a local HANDLE that corresponds to a HANDLE in a remote process, where the remote HANDLE has been marshaled
using PAL_LocalHandleToRemote().
The return value is INVALID_HANDLE_VALUE on failure, due to out-of-memory,
and so on. The results are undefined if the RHANDLE was not created using PAL_LocalHandleToRemote().
Win32 Entry Points
User32.dll
CharNextA
CharNextExA
- CodePage - either CP_ACP or CP_OEMCP.
- lpCurrentChar -
- dwFlags - always 0.
wsprintfW
The format string must support %x, %s, %d, and other C runtime standard printf formatters, plus %ws.
wsprintfA
MessageBoxW
hWnd - always NULL.
- lpText -
- lpCaption -
- uType -
- Buttons:
- MB_OK
- MB_OKCANCEL
- MB_ABORTRETRYIGNORE
- MB_YESNO
- MB_RETRYCANCEL
- Icons:
- MB_ICONEXCLAMATION
- MB_ICONINFORMATION
- MB_ICONSTOP
- MB_ICONQUESTION
- Flags:
- MB_TASKMODAL
- MB_SYSTEMMODAL
- MB_SERVICE_NOTIFICATION
- MB_SETFOREGROUND
- MB_TOPMOST
The implementation of MessageBox() should not show any UI to the user. Instead, it should take the text and any other relevant process
or thread information and log it to the host operating system's event-logging mechanism.
The return value will be:
- IDOK if the button was MB_OK.
- IDCANCEL if the button was MB_OKCANCEL.
- IDABORT if the button was MB_ABORTRETRYIGNORE.
- IDNO if the button was MB_YESNO.
- IDCANCEL if the button was MB_RETRYCANCEL.
Kernel32.dll
File I/O
CreateFileA CreateFileW
The file name will never be of the form \\.\*. This excludes named pipes,
mailslots, disk devices, and tape devices. The file name will be either a fully
qualified pathname (such as "c:\windows\myfile.txt" on Win32 or
"/etc/rotor.conf" on UNIX system-based platforms), or a partially qualified pathname
(such as "myfile.txt" or "windows\myfile.txt").
CreateFile does not need to support file names starting with "\\.\".
Therefore, named pipes, mailslots, disk devices, and tape devices are not
supported, because all of those names start with "\\.\". Files and directories
should be supported, though not with the Windows-specific "\\.\C:" format.
Communications resources and consoles are not supported through the Windows NT
names such as CON:, COM1:, CONIN$, and so on. CreateFile should only support
opening names within the file system - files, directories, and on UNIX system-based
platforms, devices mounted in the file system, such as "/dev/tty".
CreateFile does not need to map the built-in DOS device names to underlying
operating system device names.
The rationale for not mapping the DOS device names to the UNIX system-based
platform names is that,
for many standard DOS device names the devices require additional APIs for
configuring the device, and these APIs are not exposed through the PAL API.
For example, COMx: support in CreateFile does not make sense because there is no way for the code calling the PAL
CreateFile() to configure the baud rate, control flow, and other parameters.
dwShareMode & FILE_SHARE_DELETE
If passed in subsequent calls to CreateFile do not require DELETE access to
be specified for the open to succeed.
- lpFileName - see above.
- dwDesiredAccess - any combination of:
- GENERIC_READ
- GENERIC_WRITE
- 0
- dwShareMode - any combination of:
- FILE_SHARE_READ
- FILE_SHARE_WRITE
- FILE_SHARE_DELETE
- 0
- lpSecurityAccess - either NULL, or a pointer to a SECURITY_ATTRIBUTES whose lpSecurityDescriptor is NULL and whose bInheritHandle is TRUE. In this case, the handle is destined to be passed to CreateProcess as one of the STARTUPINFO standard handles and is expected to be inherited by the child. In
other words, the default is that file handles should close on execution on
computers running UNIX system-based platforms, unless bInheritHandle is TRUE.
- dwCreationDisposition - one of:
- CREATE_NEW
- CREATE_ALWAYS
- OPEN_EXISTING
- OPEN_ALWAYS
- TRUNCATE_EXISTING
- dwFlagsAndAttributes - one or more of:
- FILE_ATTRIBUTE_NORMAL
- FILE_FLAG_SEQUENTIAL_SCAN
- FILE_FLAG_WRITE_THROUGH
- FILE_FLAG_NO_BUFFERING
- FILE_FLAG_RANDOM_ACCESS
- FILE_FLAG_BACKUP_SEMANTICS
- hTemplateFile - always NULL.
FILE_FLAG_BACKUP_SEMANTICS is specified when the caller wants to open a directory as a file. The directory HANDLE is used only in
GetFileTime() or SetFileTime() calls, so the caller will request write access. However,
UNIX system-based platforms allow directories to be opened only for read access, though the directory's lastaccess and lastwrite time
can be altered through that file descriptor, so CreateFileA() might choose to ignore the dwDesiredAccess value if FILE_FLAG_BACKUP_SEMANTICS is specified.
AreFileApisANSI
CopyFileA CopyFileW
DeleteFileA DeleteFileW
MoveFileW (MoveFileA not required)
MoveFileExW (MoveFileExA not required)
- lpExistingFileName -
- lpNewFileName -
- dwFlags may be one or more of:
- MOVEFILE_COPY_ALLOWED
- MOVEFILE_REPLACE_EXISTING
CreateDirectoryA CreateDirectoryW
- lpPathName - the path name should be limited to MAX_PATH characters for
UNIX system-based platforms. MSDN documents the maximum length on Win32 to be slightly shorter than MAX_PATH.
- lpSecurityAttributes - always NULL.
RemoveDirectoryW (RemoveDirectoryA not required)
FindFirstFileA FindFirstFileW FindNextFileW FindNextFileA FindClose
Note: The HANDLE return type on Win32 is not allocated from the same pool as
other HANDLE types like events and files. It can never be passed to CloseHandle(),
it should only be passed to FindNextFileA/W() and FindClose().
GetFileAttributesA GetFileAttributesW
To support a CLI implementation, the return value must support FILE_ATTRIBUTE_DIRECTORY, -1 (for file-not-found), and FILE_ATTRIBUTE_READONLY. To support
System.IO.File.GetAttributes, the PAL needs to implement as much functionality as feasible.
GetFileAttributesExW (GetFileAttributesExA not required)
- lpFileName -
- fInfoLevelId - always GetFileExInfoStandard.
- lpFileInformation - always an OUT pointer to a WIN32_FILE_ATTRIBUTE_DATA.
SetFileAttributesA SetFileAttributesW
To correctly support System.IO.File.SetAttributes in a CLI
implementation, the PAL needs to implement as much functionality as feasible.
Attributes that cannot be supported, such as FILE_ATTRIBUTE_HIDDEN on UNIX
system-based platforms, can be silently ignored by the PAL.
WriteFile
- hFile -
- lpBuffer -
- nNumberOfBytesToWrite -
- lpNumberOfBytesWritten -
- lpOverlapped - always NULL.
GetStdHandle
The handles returned from GetStdHandle may be passed to CreateProcess in the STARTUPINFO structure.
SetEndOfFile
SetFilePointer
ReadFile
- hFile -
- lpBuffer -
- nNumberOfBytesToRead -
- lpNumberOfBytesRead -
- lpOverlapped - always NULL.
GetFileSize
CompareFileTime
SetFileTime
GetFileTime
FlushFileBuffers
FileTimeToLocalFileTime
FileTimeToDosDateTime
Note that the range of allowable dates might differ from the range available on Win32. Implementations must allow dates up to year 2037.
DosDateTimeToFileTime
GetSystemTimeAsFileTime
LocalFileTimeToFileTime
GetFileType
The return value must be FILE_TYPE_DISK for HANDLE types that represent files.
This value is really used to determine whether the HANDLE supports seek
operations.
GetConsoleCP
GetConsoleOutputCP
GetFullPathNameW GetFullPathNameA
- lpFileName -
- nBufferLength -
- lpBuffer -
- lpFilePart - may be NULL.
GetLongPathNameW
On file systems that do not support long vs. short pathnames, or if the short path does not exist, it should return 0 without altering the contents of lpszLongPath.
GetTempFileNameA GetTempFileNameW
- lpFileName - must not be NULL
- lpPrefixString -
- uUnique - always 0
- lpTempFileName - the case of the returned file name is implementation-specific, though it must be valid on the current platform. The extension
can be ".tmp" or ".TMP", for example.
GetTempPathA GetTempPathW
GetCurrentDirectoryA GetCurrentDirectoryW
SetCurrentDirectoryA SetCurrentDirectoryW
LockFile
UnlockFile
GetDiskFreeSpaceW
- lpRootPathName - on Windows, will be a path of the form "x:\" or UNC path with a trailing '\' character. On
UNIX system-based platforms, it will be a fully-qualified path name, though the path
might not exist.
- lpSectorsPerCluster -
- lpBytesPerSector -
- lpNumberOfFreeClusters - the caller will ignore this output value.
- lpTotalNumberOfClusters - the caller will ignore this output value.
The SSCLI managed assembly location implementation uses this API to round file sizes up to their actual on-disk
size based on the BytesPerSector*SectorsPerCluster, that must equal the minimum allocation granularity for files stored under lpRootPathName. This value is known as the "cluster size" on Windows, and the "frag-size" on
UNIX system-based platforms.
SearchPathA SearchPathW
- lpPath - always non-NULL. Paths within the string are separated by the native platform's environment variable separator character (";" on Win32, ":" on
UNIX system-based platforms).
- lpFileName - always non-NULL, may be relative or fully qualified.
- lpExtension - always NULL.
- nBufferLength -
- lpBuffer -
- lpFilePart - may be NULL or non-NULL. If the pointer is non-NULL, the value written into the variable is implementation-defined: the PAL
might not write anything, or it might write NULL, or it might write the value matching the MSDN documentation, or it
might write any other value.
Threads and Processes
CreateSemaphoreA CreateSemaphoreW ReleaseSemaphore
- lpSemaphoreAttributes - always NULL.
- lpName - always NULL, so the semaphore is always scoped within a process.
CreateEventA CreateEventW
- lpEventAttributes - always NULL.
- bManualReset - either TRUE or FALSE.
- bInitialState - either TRUE or FALSE.
- lpName - See Side-by-side support for more information.
SetEvent
ResetEvent
OpenEventW (OpenEventA not required)
- dwDesiredAccess - always EVENT_ALL_ACCESS.
- bInheritHandle -
- lpName - always non-NULL. See Side-by-side support for more information.
CreateMutexW (CreateMutexA not required)
- lpMutexAttributes - always NULL.
- bInitialOwner -
- lpName - may be non-NULL. See Side-by-side support for more information.
ReleaseMutex
GetCurrentProcessId
CreateProcessA CreateProcessW
- lpApplicationName - always NULL.
- lpCommandLine -
- lpProcessAttributes - either NULL or a pointer to a SECURITY_ATTRIBUTES whose lpSecurityDescriptor is NULL and whose bInheritHandle is TRUE. Note that the newly created process and thread handles do not need to be inherited by the child - the security descriptors may be ignored entirely.
- lpThreadAttributes - always NULL.
- bInheritHandles - if FALSE, then no handles are inherited by the child. If TRUE, then the standard in/out/error handles must be inherited by the child.
- dwCreationFlags - either 0 or CREATE_NEW_CONSOLE, which may be ignored on systems which do not support multiple console windows
- lpEnvironment -
- lpCurrentDirectory -
- lpStartupInfo - the cb field will be set. dwFlags may be 0 or
STARTF_USESTDHANDLES. If STARTF_USESTDHANDLES is set, then hStdOutput,
hStdInput, and hStdError will be Win32 HANDLEs for the standard in/out/error
streams, instead of the parent's standard in/out/error streams.
- lpProcessInformation - on successful return from this API, only the
hProcess, ProcessId, and hThread fields must be filled in.
The CreateProcess implementation must probe the type of the binary pointed to by
the lpCommandLine parameter. If the file is an IL-only PE/COFF image, then CreateProcess must prepend the fully-qualified path to "clix " (found in the PAL_GetPALDirectoryW() directory) to the lpCommandLine parameter before launching the child process, so that the IL-only binary is launched within a new
SSCLI process. Otherwise, the lpCommandLine should be considered to be a native system executable and launched as such.
WaitForSingleObject
- hHandle - basically any waitable HANDLE type (process, event, and so on.).
- dwMilliseconds can be INFINITE, or a value in milliseconds.
GetExitCodeProcess
The return code from the child process might be truncated on some platforms, to
the size of that platform's process exit code data type. The PAL
GetExitCodeProcess will sign-extend the native platform's process exit code data
type up to 32 bits and return as a DWORD. For PAL APIs that exit the process
and specify a return code, if the return value is outside of the valid range for
the host platform, the PAL will truncate the return value to fit. In other
words, on platforms where the return value is a signed char, return values less than -128 will become 128, and return values greater than 127 will become 127.
DuplicateHandle
- hSourceProcessHandle -
- hSourceHandle - may be a process handle or GetCurrentThread().
- hTargetProcessHandle -
- lpTargetHandle will be non-NULL.
- dwDesiredAccess - can be ignored.
- bInheritHandle - always FALSE.
- dwOptions will always be DUPLICATE_SAME_ACCESS.
If hSourceProcessHandle is not the current process, then hSourceHandle is a local handle created via PAL_RemoteHandleToLocal. Similarly, if hTargetProcessHandle is not the current process, then lpTargetHandle should contain a HANDLE value suitable for passing to PAL_LocalHandleToRemote for remoting.
For cases where either source or destination is not the current process, DuplicateHandle() can be implemented as a simple copy of hSourceHandle to *lpTargetHandle, if PAL_LocalHandleToRemote() and PAL_RemoteHandleToLocal() are used to remote HANDLE
types. Alternatively, if the DuplicateHandle() can perform cross-process HANDLE duplication, then PAL_LocalHandleToRemote() and PAL_RemoteHandleToLocal() can be implemented as
NO-OPs, returning the HANDLE value unmodified.
GetCurrentProcess
GetCurrentThreadId
Sleep
SleepEx
SwitchToThread
CreateThread
- lpSecurityAttributes - always NULL.
- dwStackSize - Treat this as the minimal size the caller expects for the stack - the implementation of CreateThread may choose a larger value.
- lpStartAddress -
- lpParameter -
- dwCreationFlags - may be 0 or CREATE_SUSPENDED.
- lpThreadId
See PAL_Initialize() for information on the initial floating-point environment for the newly-created thread.
Note that the lpThreadId pointer must be updated by the CreateThread()
implementation before the newly-created thread is allowed to run. For
example, if the lpThreadId points to a global variable, the new thread must be able to confirm that its GetCurrentThreadId() value matches the contents of the global
variable.
SuspendThread
- hThread - always within the current process, and never the current thread.
ResumeThread
- hThread - either within the current process, or the hThread returned from CreateProcess(), if the child was created as suspended.
QueueUserAPC
GetThreadContext
- hThread - Always represents a thread within the current process. If the hThread is not the current thread, then the thread will have been suspended via SuspendThread().
- pContext - Must support at least CONTEXT_CONTROL and CONTEXT_INTEGER.
SetThreadContext
- hThread - always a thread within the current process and never the current thread. The thread will always be suspended at the time of the call.
- pContext - may have CONTEXT_CONTROL and/or CONTEXT_INTEGER set.
GetCurrentThread
GetThreadPriority
SetThreadPriority
- hThread -
- nPriority - must support all of the THREAD_PRIORITY_* constants, and must
map THREAD_PRIORITY_IDLE to the least-priority value the host operating system
allows, as the common language runtime sets threads to THREAD_PRIORITY_IDLE
then busy-waits with a Sleep(0) loop until the garbage collection thread has run, with the
garbage collection thread at a priority greater than idle.
WaitForMultipleObjectsEx
This API may be called with any handle type or types that are supported in the
PAL and are documented as waitable in the Win32 API documentation.
WaitForMultipleObjects
TerminateProcess
- hProcess -
- uExitCode - may be truncated to a signed char. For details see GetExitCodeProcess(). This parameter may be ignored if hProcess is not the current process. In that case, the exit code of the terminated process may be any value except zero.
ExitThread
GetProcessTimes
- hProcess - always the return from GetCurrentProcess().
- lpCreationTime - unused.
- lpExitTime - unused.
- lpKernelTime -
- lpUserTime -
TlsGetValue
This API should not be logged, to avoid a performance hit, as this API is called frequently.
TlsSetValue
TlsFree
TlsAlloc
The PAL must guarantee that at least 32 TLS slots are available to the calling application.
EnterCriticalSection TryEnterCriticalSection
These need to support recursion on enter.
LeaveCriticalSection
DeleteCriticalSection
InitializeCriticalSection
SetErrorMode
- uMode - may be one or more of:
- SEM_FAILCRITICALERRORS
- SEM_NOOPENFILEERRORBOX
The PAL may implement this function as a NO-OP, ignoring the input parameter, and always returning 0.
ExitProcess
- ExitCode - may be truncated to a signed char. For details see
GetExitCodeProcess.
WriteProcessMemory
The PAL does not need to enforce memory protection: attempts to write to read-only pages in the destination process may either succeed by altering the page to be read/write, or may cause WriteProcessMemory to return a failure code.
OpenProcess
- dwDesiredAccess - always PROCESS_ALL_ACCESS
- bInheritHandle - always FALSE
- dwProcessID - process ID to open. If the process is not a PAL process, then OpenProcess may fail
This API is used by cordbg's "attach" command, to attach the debugger to the specified process.
Memory Management
CreateFileMappingA CreateFileMappingW
- hFile - may be file handle or -1 (in other words, allocate from swap).
- lpAttributes - always NULL.
- flProtect - one or more of PAGE_READONLY, PAGE_READWRITE, or PAGE_WRITECOPY.
- dwMaximumSizeHigh is always 0.
- dwMaximumSizeLow will be non-zero if the hFile is -1 (in other words, allocate from swap).
- lpName - may be NULL or non-NULL. If hFile is not -1, then lpName will always be NULL. If hFile is -1 then lpName may or may not be NULL. See Side-by-side support for more information.
OpenFileMappingA OpenFileMappingW
- dwDesiredAccess - be one or more of FILE_MAP_READ , FILE_MAP_WRITE, or FILE_MAP_ALL_ACCESS.
- bInheritHandle -
- lpName - See Side-by-side support for more information.
MapViewOfFile
- hFileMappingObject - the return value from a CreateFileMapping() or
OpenFileMapping() call.
- dwDesiredAccess will be one of the following:
- FILE_MAP_WRITE
- FILE_MAP_READ
- FILE_MAP_ALL_ACCESS
- FILE_MAP_COPY (only if the mapping was created with PAGE_WRITECOPY)
- dwFileOffsetHigh - always 0.
- dwFileOffsetLow - always 0.
- dwNumberOfBytesToMap - 0 or nonzero.
UnmapViewOfFile
LoadLibraryA LoadLibraryW
These APIs will be used only to load and call unmanaged dynamic libraries. There is no requirement that they support loading of IL-only PE/COFF files. Like Win32 LoadLibrary, the PAL must first search the directory that the application was loaded from, but beyond that, it may search whatever directories a native shared-library loader would use, in whatever order it would search. It need not use the PATH environment variable.
FreeLibrary
Calls to FreeLibrary() made after PAL_Terminate() or ExitProcess() must be ignored and treated as success regardless of the validity of the module handle. The application may call
FreeLibrary() from within a DllMain() routine that is handling a DLL_PROCESS_DETACH message.
FreeLibraryAndExitThread
GetProcAddress
GetProcAddress must support binding to both functions and data by name. Binding by ordinal is not required, though some implementations may support it. It is illegal to attempt to bind by ordinal on implementations that do not support it.
DisableThreadLibraryCalls
Calls to DisableThreadLibraryCalls made after PAL_Terminate() or ExitProcess() must be ignored and treated as success regardless of the validity of the module handle. The application may call
DisableThreadLibraryCalls() from within a DllMain() routine that is handling a DLL_PROCESS_DETACH message.
GetModuleFileNameA GetModuleFileNameW
- hModule - always NULL
- lpFileName -
- nSize -
VirtualAlloc
- lpAddress - may be NULL or non-NULL.
- dwSize -
- flAllocationType - may be one or more of:
- MEM_COMMIT
- MEM_RESERVE
- MEM_TOP_DOWN (can be ignored)
- flProtect - may be one of:
- PAGE_NOACCESS
- PAGE_READONLY
- PAGE_READWRITE
- PAGE_EXECUTE
- PAGE_EXECUTE_READ
- PAGE_EXECUTE_READWRITE
VirtualFree
VirtualProtect
- lpAddress - any usermode address (may not have been allocated through VirtualAlloc()
- it may be an address within a mapped section or within a system-native DLL's code)
- dwSize -
- flNewProtect - any of:
- PAGE_NOACCESS
- PAGE_READONLY
- PAGE_READWRITE
- PAGE_EXECUTE
- PAGE_EXECUTE_READ
- PAGE_EXECUTE_READWRITE
- Any other values returned back in lpflOldProtect.
- lpflOldProtect -
VirtualQuery
If the address to query is not contained within an allocation created using
VirtualAlloc(), then VirtualQuery() may return MEM_FREE, even if the address is not
available for use by VirtualAlloc(). If the memory is in use as a DLL, heap or mapped file,
VirtualQuery() may return MEM_FREE rather than MEM_COMMIT. This is compatible with the usage patterns in clr\src\vm\gcsmp.cpp and clr\src\utilcode\util.cpp.
In the event of a return of MEM_FREE for the unknown region, the returned MEMORY_BASIC_INFORMATION.RegionSize should be zero.
ReadProcessMemory
- hProcess - If this is GetCurrentProcess(), then the ReadProcessMemory is being used as a test to determine if the lpBaseAddress points to committed, readable memory. If hProcess is not the current process, then the debugger
is calling it to read memory from the debuggee's address space.
GetProcessHeap
This can return any value - the return value is passed to HeapAlloc() or
HeapFree()
only as a cookie.
HeapAlloc
- hHeap -
- dwFlags - may be either 0 or HEAP_ZERO_MEMORY.
- dwBytes -
HeapReAlloc
- hHeap -
- dwFlags - always 0.
- lpMem -
- dwBytes -
HeapFree
- hHeap -
- dwFlags - always 0.
- lpMem -
LocalAlloc
- uFlags - always 0.
- uBytes -
LocalFree
FlushInstructionCache
The range of memory specified by lpBaseAddress + dwSize must be committed.
- hProcess - always the current process.
- lpBaseAddress -
- dwSize -
RtlMoveMemory
CopyMemory
Locale Information
GetStringTypeExW
- Locale - always LOCALE_USER_DEFAULT
- dwInfoType - always CT_CTYPE1
- lpSrcStr - always a pointer to one Unicode character
- cchSrc - always 1
- lpCharType - the callers bitwise-AND the [out] value with C1_DIGIT and C1_SPACE to determine if the character is a digit or white space.
CompareStringW
- Locale - always 0x0409
- dwCmpFlags - always NORM_IGNORECASE|NORM_IGNOREWIDTH.
- lpString1 -
- cchCount1 -
- lpString2 -
- cchCount2 -
GetLocaleInfoW
- Locale - always LOCALE_NEUTRAL.
- LCType - any one of:
- LOCALE_SDECIMAL
- LOCALE_STHOUSAND
- LOCALE_ILZERO
- LOCALE_SCURRENCY
- LOCALE_SMONDECIMALSEP
- LOCALE_SMONTHOUSANDSEP
- lpLCData -
- cchData -
GetACP
GetCPInfo
- CodePage - code page for which to get information. CP_UTF8 and CP_UTF7 code pages do not need to be handled, but the code page of the console does.
- lpCPInfo - on return, only the MaxCharSize field needs to be filled in.
IsValidCodePage
WideCharToMultiByte
- CodePage - any valid codepage.
- dwFlags - always 0.
- lpWideCharStr -
- cchWideChar -
- lpMultiByteStr -
- cbMultiByte -
- lpDefaultChar - always NULL.
- lpUsedDefaultChar - always NULL.
MultiByteToWideChar
- CodePage - any valid code page.
- dwFlags - either one or more of MB_PRECOMPOSED and MB_ERR_INVALID_CHARS.
- lpMultiByteStr -
- cbMultiByte -
- lpWideCharStr -
- cchWideChar -
GetSystemDefaultLangID
GetUserDefaultLangID
SetThreadLocale
GetThreadLocale
GetUserDefaultLCID
GetTimeZoneInformation
IsValidLocale
- Locale -
- dwFlags - either LCID_SUPPORTED or LCID_INSTALLED.
GetConsoleOutputCP
If the console code page is not the same as GetACP(), the C# compiler tries to remap its output strings by first converting to Unicode, then into ANSI using the console's code page.
IsDbcsLeadByteExW
GetCalendarInfoW
- Locale - always LOCALE_USER_DEFAULT.
- Calendar - one of the CAL_ constants from 1 to 13..
- CalType - always CAL_ITWODIGITYEARMAX|CAL_RETURN_NUMBER
- lpCalData - always NULL.
- cchData - always 0.
- lpValue - pointer to the buffer to hold the return value.
If this API fails by returning 0, the natural language support (NLS) code will fall back to a set of default values compatible with the
common language runtime when running on Windows 95.
GetDateFormatW
- Locale - either the return value from GetSystemDefaultLCID() or 0x0404 (SUBLANG_CHINESE_TRADITIONAL/LANG_CHINESE).
- dwFlags - always DATE_USE_ALT_CALENDAR.
- lpDate - always NULL.
- lpFormat - always Unicode "gg".
- lpDateStr - pointer to the string buffer to hold the result.
- cchDate - length of the lpDateStr buffer in Unicode characters.
This API will only be called only from within System.Globalization.DateTimeFormatInfo when the calendar is CAL_TAIWAN.
Miscellaneous
OutputDebugStringA OutputDebugStringW
If the PAL can determine whether a native debugger is present then the following
is applicable:
- If the debugger is present, output should go to the debugger.
- Otherwise the API should produce no output.
If the PAL cannot determine whether a native debugger is present then the
following is applicable:
- If the environment variable PAL_OUTPUTDEBUGSTRING is set, output should go
to stderr.
- Otherwise the API should produce no output.
DebugBreak
lstrcatW
lstrcpyW
lstrlenA lstrlenW
lstrcpynW
GetEnvironmentVariableA GetEnvironmentVariableW
SetEnvironmentVariableA SetEnvironmentVariableW
CloseHandle
RaiseException
- dwExceptionCode -
- dwExceptionFlags - can be ignored. All explicitly raised exceptions may be considered noncontinuable.
- nNumberOfArguments -
- lpArguments -
GetTickCount
QueryPerformanceCounter
QueryPerformanceFrequency
SetUnhandledExceptionFilter
InterlockedExchange
This API should not be logged to avoid a performance hit because this API is called frequently.
InterlockedExchangePointer
This API should not be logged to avoid a performance hit because this API is called frequently.
InterlockedDecrement
This API should not be logged to avoid a performance hit because this API is called frequently.
InterlockedIncrement
This API should not be logged to avoid a performance hit because this API is called frequently.
InterlockedCompareExchange
This API should not be logged to avoid a performance hit because this API is called frequently.
InterlockedCompareExchangePointer
This API should not be logged to avoid a performance hit because this API is called frequently.
IsBadReadPtr
IsBadWritePtr
IsBadCodePtr
GetSystemTime
FormatMessageW (FormatMessageA not required)
- dwFlags - may be any of:
- FORMAT_MESSAGE_FROM_SYSTEM
- FORMAT_MESSAGE_FROM_STRING
- FORMAT_MESSAGE_IGNORE_INSERTS
- FORMAT_MESSAGE_ARGUMENT_ARRAY
- FORMAT_MESSAGE_ALLOCATE_BUFFER
- Note: if both *_FROM_STRING flags are passed, the function should return an error. Win32 FormatMessageW appears to silently ignore *_FROM_STRING if both flags are passed.
- lpSource - set only if FORMAT_MESSAGE_FROM_STRING is set in dwFlags.
- dwMessageID - a LastError code or HRESULT.
- dwLanguageID - may be 0, or MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT).
- lpBuffer -
- nSize -
- variable arguments - if FORMAT_MESSAGE_ARGUMENT_ARRAY is set then the first arg is a pointer to an array of DWORD_PTR arguments. Otherwise this is a printf-style list of arguments.
You may implement
FORMAT_MESSAGE_FROM_SYSTEM by dynamically loading the PAL
runtime (PAL RT) and then calling the satellite APIs (for details see Localized Resource Strings) to map the Win32 error
codes to text. Because the PAL is built and run before the PAL RT has
been built, the PAL may not statically link to the PAL RT and must be
robust against failures to load it. Appendix A
has information on how to generate a rotor_pal.satellite file containing
the error-to-text mappings using a Windows operating system.
Note: Windows 2000 and Windows XP have different behavior for format strings that use the printf-style format specifier "lS". For example, "%3!lS!". Use of
the "lS" specifier in the PAL FormatMessageW is disallowed; the implementation is PAL-specific.
GetLastError
The error codes supported by the PAL are listed in rotor_pal.h.
SetLastError
FreeEnvironmentStringsW (FreeEnvironmentStringsA not required)
GetEnvironmentStringsW (GetEnvironmentStrings not required)
GetCommandLineW (GetCommandLineA not required)
GetVersionExA GetVersionExW)
Only the dwMajorVersion, dwMinorVersion, and dwPlatformID need to be set. On
UNIX system-based platforms, dwPlatformId should be VER_PLATFORM_UNIX.
GetSystemInfo
- lpSystemInfo - must fill in the following:
- dwNumberOfProcessors
- dwPageSize
- lpMinimumApplicationAddress (the lowest address where memory could be possibly be committed).
- lpMaximumApplicationAddress (the highest address where memory could possibly be committed).
SetConsoleCtrlHandler
- HandlerRoutine - must be non-NULL.
- Add -
Note that the PAL must notify the callback routine of CTRL+C events (or whatever key sequence signals a process to exit), but the PAL may not notify the callback routine of other events such as window-close or user logoff. The intent is to allow command-line tools to clean up any temporary output files in the event.GenerateConsoleCtrlEvent
- dwCtrlEvent -
- dwProcessGroupId - must be 0.
Raise a CTRL+C or CTRL+Break event in the current process, used to test the SetConsoleCtrlHandler() implementation. The current process, or the current process and its subgroup may receive the event, depending on the PAL implementation.
CreatePipe
- hReadPipe -
- hWritePipe -
- lpPipeAttrbutes - pointer to a SECURITY_ATTRIBUTES whose lpSecurityDescriptor is NULL and whose bInheritHandle is TRUE. The handle is destined to be passed to CreateProcess as one of the STARTUPINFO standard handles and is expected to be inherited by the child
- nSize - always 1024, and the value may be ignored
C Runtime
memcmp
memset
memcpy
memmove
strlen
wcstol
wcscpy
wcslen
wcsrchr
wcscat
wcsncmp
wcschr
_wcsnicmp
swprintf
malloc
free
_alloca
atexit
operator new
operator delete
qsort
_fdopen
_close
fclose
wcspbrk
sprintf
feof
ferror
fread
fwrite
_swab
_stricmp
fgets
fputs
_mbslen
_mbsinc
_mbsninc
_mbsdec
isspace
iswspace
isprint
vprintf
fprintf
fwprintf
vsprintf
_vsnprintf
_snprintf
vswprintf
_vsnwprintf
_snwprintf
sscanf
strstr
_strlwr
wcsstr
_wcslwr
_putw
fseek
ftell
_getw
iswdigit
isxdigit
iswxdigit
bsearch
_finite
_isnan
realloc
memchr
strcmp
strncmp
_strnicmp
strcat
strncat
strcpy
strncpy
strchr
strrchr
strpbrk
atoi
atol
tolower
toupper
towupper
wcscmp
wcsncat
wcsncpy
_itow
_i64tow
_ui64tow
iswupper
iswprint
towlower
fflush
isalpha
isalnum
isupper
islower
__iscsym
strtok
strspn
strtoul
wcstok
strcspn
getenv
_putenv
_rotl
_rotr
abs
log
log10
exp
pow
acos
asin
atan
atan2
cos
sin
tan
cosh
sinh
tanh
fmod
floor
fabs
getc
ungetc
_ecvt
modf
ctime
rand
srand
exit
The exit code may be truncated to a signed char. For details, see
GetExitCodeProcess.
strtod wcstod
If an overflow occurs, MSDN states that these routines return +/-HUGE_VAL. On
the PAL, the value of HUGE_VAL is undefined. Thus on overflow strtod and wcstod must return a valid double-precision value, but the value does not need to be the same across different PAL implementations. There is no symbolic name "HUGE_VAL" in the PAL.
atof
The 'nnne+nnn' format will not be supported. Calls must use only 'nnnd+nnn'.
_splitpath _wsplitpath _makepath _wmakepath
On host operating systems that don't support drive letters, the "drive" parameter must always be either NULL or a pointer to an empty string. By supporting "" as a drive string, _makepath can be used on the output of a previous _splitpath call.
_fullpath
- abspath - must be non-NULL.
- relpath -
- maxLength -must be less than or equal to _MAX_PATH.
swscanf
Scan string may be "%[^,],%[^,],%s" or "%u%n" or "%[^,],%[^,],%[^,],%c,%c".time
localtime
mktime
_gcvt
- value -
- ndec - either 8 or 17. If the caller passes 8, then the value is really a 'float' type that was upcast to 'double' immediately before the call. If the value is 17, then the value is truly a 'double'.
- buffer -
_open_osfhandle
- osfhandle - always a pipe handle returned from CreatePipe().
- flags - always _O_RDONLY
wcstoul
On overflow or underflow, it must return ULONG_MAX and set errno to ERANGE.
errno
The only value that must be supported is ERANGE, in the event that wcstoul() fails due to overflow. Other values of errno are allowable, but no constants are defined - the PAL is free to return just 0 and ERANGE, or 0, ERANGE, and some other nonzero values. In the PAL header file, errno is a macro
that calls PAL_errno() to get the pointer to the per-thread errno value, then references that pointer.
fopen
- filename -
- mode - must support "r", "w", "a", "r+", "w+", "a+", plus "t" and "b" translation modes.
_wfopen
- filename -
- mode - must support "wb", "rb", and "rt" modes.
Wsock32.dll, Ws2_32.dll
gethostbyname
gethostbyaddr
- addr -
- len - always sizeof(int).
- type - always 2, AF_INET.
gethostname
inet_addr
getpeername
getsockopt
- s -
- level - see below.
- optname - see below.
- optval -
- optlen -
getsockopt must support the following level/optname combinations:
- SOL_SOCKET: SO_LINGER, SO_RECVBUF, SO_SNDBUF, SO_RECVTIMEO, SO_SNDTIMEO.
- IPPROTO_TCP: TCP_NODELAY.
The PAL may support more level and optname values; these are the minimum set. The PAL may return WSAEINVAL if the level is unknown, or WSAENOPROTOOPT if the option is unsupported by the protocol family.
setsockopt
- s -
- level - see below.
- optname - see below.
- optval -
- optlen -
setsockopt must support the following level/optname combinations:
- SOL_SOCKET: SO_LINGER, SO_RECVBUF, SO_SNDBUF, SO_RECVTIMEO, SO_SNDTIMEO, SO_BROADCAST
- IPPROTO_IP: IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_TTL
- IPPROTO_TCP: TCP_NODELAY
The PAL may support more level and optname values, these are the minimum set. The PAL may return WSAEINVAL if the level is unknown, or WSAENOPROTOOPT if the option is unsupported by the protocol family.
connect
If the 'namelen' parameter is invalid and the API fails, the exact failure code returned from
GetLastError() is implementation-dependent.
send
recv
closesocket
accept
listen
bind
shutdown
If the socket is connectionless (as in DGRAM) and has not had connect() called on it, the PAL may either succeed or return WSAENOTCONN.
sendto
If the socket is connectionless (in other words, DGRAM), there are several implementation-specific options:
- If connect() has been called on the socket, then sendto may either succeed or fail with WSAEISCONN, depending on the PAL implementation.
- If MSG_OOB is specified, then the PAL may either ignore MSG_OOB and send the data as-is, or it may fail the call with WSAEOPNOTSUPP.
- If the destination is INADDR_ANY, then the behavior is implementation-defined. The call may fail, or it may succeed.
If the socket is connection-oriented but not connected, the behavior is implementation-defined: PAL may either fail the call or may connect the socket.
The PAL need not validate the namelen parameter.
recvfrom
getsockname
select
- nfds - always 0
- readfds -
- writefds -
- exceptfds -
- timeout -
socket
If multiple parameters are incorrect, precisely which error is reported is
implementation-defined. For example, different implementations may validate parameters in a different order, affecting which error code is returned.
WSAStartup
- wVersionRequested - always 0x0202
- lpWSAData - No fields are required; the caller allocates a buffer, passes it to WSAStartup,
and then frees it without examining any fields.
WSACleanup
WSASend
- s -
- lpBuffers -
- dwBufferCount -
- lpNumberOfBytesSent -
- dwFlags -
- lpOverlapped - always non-NULL.
- lpCompletionRoutine - always NULL.
WSASendTo
- s-
- lpBuffers -
- dwBufferCount - always 1.
- lpNumberOfBytesSent -
- dwFlags -
- lpTo -
- iToLen -
- lpOverlapped - always non-NULL.
- lpCompletionRoutine - always NULL.
WSARecv
- s -
- lpBuffers -
- dwBufferCount - always 1.
- lpNumberOfBytesRecvd -
- lpFlags -
- lpOverlapped - always non-NULL.
- lpCompletionRoutine - always NULL.
WSARecvFrom
- s -
- lpBuffers -
- dwBufferCount - always 1.
- lpNumberOfBytesRecvd -
- lpFlags -
- lpFrom -
- lpFromLen -
- lpOverlapped - always non-NULL.
- lpCompletionRoutine - always NULL.
WSAEventSelect
- s -
- hEventObject -
- lNetworkEvents - either FD_CONNECT or FD_SELECT.
WSASocketA
- af -
- type -
- protocol -
- lpProtocolInfo - always NULL.
- g - always 0.
- dwFlags - always WSA_FLAG_OVERLAPPED.
WSAIoctl
- s -
- dwIOControlCode - can be any value that Winsock2 supports. The
managed type of this parameter is 'int'; there is no enum.
- lpvInBuffer -
- cbInBuffer -
- lpvOutBuffer -
- cbOutBuffer -
- lpcbBytesReturned -
- lpOverlapped - always NULL
- lpCompletionRoutine - always NULL
The PAL need only implement ioctls that reasonably map to the host operating system's socket code. There are no functional requirements from within
SSCLI itself: the API is never called, except on behalf of a managed application calling
System.Net.Sockets.IOControl.
WSAEnumNetworkEvents
- s -
- hEventObject -
- lpNetworkEvents -
ioctlsocket
- s -
- cmd - either FIONREAD or FIONBIO
- argp -
Implementation Details
API Call Tracing
The PAL should include API tracing in the debug build. API trace logs will be an invaluable debugging tool, for understanding bugs in both the CLI and the PAL.
Tracing should be controlled by an environment variable that is read during PAL per-process initialization.
If the variable, PAL_API_TRACING is set, it is either a file name to write to, or "stdout" or "stderr". If the name is "stdout" or "stderr" then logging should be performed to the C runtime stdout or stderr file. Otherwise, the PAL should open the file, truncate it if it exists, and write the trace information to that file.
Ideally, each API should log the following information:
- One line containing the current ThreadId, the API name, and the names and values of each of the parameters. It is not necessary to do deep logging and print the contents of all byref structures,
and so on, but inbound strings like file names should be logged to assist debugging. This should be logged on entry into the API.
- A separate line containing the current ThreadId, the API name, the return type, and the value of the API return, logged on exit from the API. If the contents of an out byref parameter are particularly relevant to the API, its name and value should also be logged on the same line.
Care must be taken to format the entire line to be logged into one buffer before being written to the log file, so concurrent API calls on different threads do not have logging interleaved together within a single line.
Example:
000000ba CopyFileW(lpExistingFileName=001259a0 L"test.txt", lpNewFileName=00126762 L"baz.txt", bFailIfExists=0)
000000c0 FindClose(hFindFile=10a3cdb0)
000000c0 FindClose BOOL 1
000000ba CopyFileW BOOL 1
File and Path Names
On UNIX system-based platform file systems, any PAL APIs operating on file or path names must be prepared to accept DOS/Win32-style pathnames and translate them into paths acceptable for the host
file system. In particular:
- The "\" character used as a path separator should be converted to "/" on
UNIX system-style file systems. Thus, "\sscli/src\test/pal" should be considered to be four distinct components of the path name - "sscli", "buiild", "test", and "pal". The equivalent UNIX
system-style path would be "/sscli/src/test/pal".
- The trailing dot character "." should be stripped from any directory or
file name. Thus "/sscli./test/pal." should be converted to "/sscli/test/pal" on UNIX
system-based platform file systems.
- The wildcard character pattern "*.*" should be replaced by "*" for APIs where
wildcard characters are appropriate, assuming the file name expansion is similar to
"glob" on UNIX system-based platforms. Thus DOS/Win32 "*.*" should expand to contain all files in the following list: "test", "test.bat", "test.c". The wildcard pattern "*." is subject to the second rule and should be replaced
with "*".
- DOS/Win32 drive letters should never appear. Thus "c:\test.txt" can be safely translated to "c:/test.txt" on
UNIX system-based platforms, meaning a relative directory name "c:" and a name "test.txt" below it.
However, the PAL does not need to implement case insensitivity for compatibility with Win32. If the underlying
file system is case-sensitive, then the PAL file system APIs should also be case-sensitive.
On Win32, CreateFile(), fopen, and other file-open APIs all fail if the string parameter refers to a directory instead of a file.
On file systems that support symbolic links, the PAL APIs must always open the
destination of the link (in other words, follow the link and
operate on what the link references), rather than operate on the link itself.
PAL file I/O APIs do not need to support file names starting with "\\.\" or names of DOS devices. Therefore, named pipes, mailslots, disk devices, and tape devices are not supported,
because all of those names start with "\\.\", and names of DOS devices such as "lpt1", "aux", "com1" need not be supported. The PAL does not need to map the built-in DOS device names to underlying
operating system device names.
The rationale for not mapping the DOS device names to the UNIX system-style names is that
many standard DOS device names require additional APIs for
configuring the device, and these APIs are not exposed through the PAL API.
Thus, COMx: support in CreateFile() does not make sense because there is no way for the code calling the PAL CreateFile to configure the baud rate, control flow, and other parameters.
HANDLE Types
The allocator used to manage HANDLE types should not need to be robust against
mis-use such as double-close of a HANDLE or use of a HANDLE after it was closed.
However, it will likely be worthwhile to code some robustness into the free
version and add some more expensive validation code into the checked and
fastchecked versions.
On 64-bit systems, sizeof(HANDLE)==8, and consumers of HANDLE types will never truncate. On
Microsoft Win64®, sizeof(HANDLE)==8, but the upper 4 bytes can be truncated, and then the 32-bit value can be sign-extended to recreate a HANDLE.
LastError
The PAL must accept following error codes as SetLastError values and may set the lasterror value to any of these codes as the result of an error encountered during execution of a PAL API.. The full list can be found in rotor_pal.h - search for "winerror.h codes" and "Socket error codes".
Printf Format Specifiers
All printf-style routines may share a single format specification list. The specification list should follow
ANSI-C, with the following exceptions and notes:
- %e and %E - the precision of the mantissa and exponent are dependent on the PAL implementation.
- %p - must exactly follow Win32's, printing 8 or 16 hex digits with no leading "0x".
- %ws, %wc, and %w must be supported, to print Unicode strings and characters.
- %I64 - must be supported, to print an __int64.
- %qu - does not need to be supported.
Use of Resource After It Has Been Freed
If the calling application allocates a resource (HANDLE, socket, heap allocation,
and so on.), uses the resource, and then frees it (using CloseHandle(),
close, free,
and so on), the behavior of subsequent API calls is undefined, if the calling application continues to pass the now-freed resource handle. The PAL may crash, assert, terminate the process, corrupt memory, falsely report success, or choose any other kind of behavior. Where reasonable, the PAL should be robust against such errors in the calling application, but it is not a requirement.
Case Sensitivity in Environment Variables
Environment-variable APIs such as getenv, putenv, GetEnvironmentVariableA, and
the like. are case-insensitive on Win32. The PAL may implement them as case-sensitive if the host operating system's environment variables are case-sensitive.
Side-by-Side: Named Shared Objects and the User Configuration Directory
In order to support multiple different SSCLI processes running side-by-side under the same user logon account, each PAL implementation must use its own isolated namespace for named shared objects. Named shared objects can be created
using APIs such as CreateEventW(), CreateMutexW(), and CreateFileMappingA().
Isolation should be per-PAL, as determined by examining the full path to the PAL .dll/.so/.dylib file. Therefore, if multiple processes use /usr/bin/librotor_pal.so, then those processes should share one namespace, while processes using ~/experimental/librotor_pal.so should share a separate namespace. One possible implementation is to hash the fully-qualifed pathname to the PAL .dll/.so/.dylib into a short string
that can then be merged with the shared object name.
The additional uniqueness string must not exceed 50 characters in length. If the named object name plus the uniqueness string exceed MAX_PATH, the PAL API may return failure, with LastError set to ERROR_FILENAME_EXCED_RANGE.
The per-user configuration directory returned by PAL_GetUserConfigurationDirectoryW() should also be per-PAL.
General Issues
I/O Completion Ports and Async I/O
I/O completion ports do not map well onto UNIX system-based platforms. Asynchronous I/O has very limited support
in general, and the APIs to use it are not standardized. For example, POSIX 4 aio_* APIs exist on Linux, but each asynchronous file open creates a new thread, causing very poor performance if a large number of files are open. I/O completion ports and asynchronous I/O will not be exposed
through the PAL.
The framework classes expose asynchronous I/O through the base Stream.BeginRead and
Stream.BeginWrite methods, and the FileStream class derives from Stream. The implementation of
FileStream's BeginRead and BeginWrite
methods will automatically fall back to synchronous reads and writes if
asynchronous is not available.
Out-of-Stack
On Win32, the common language runtime can use the guard page at the bottom of the stack to understand where the stack bounds are and avoid hitting the end of stack.
Exceptions
On non-Win32 platforms, Win32 structured exception handling (SEH) is not supported. Instead, the PAL implements a subset of SEH
using a combination of compile-time macros and runtime help. The goal is to replace code like:
... local variable declarations
try {
... code which references locals from above
} except ( ExceptionFilter() ) {
... code which references locals from above
}
with:
... local variable declarations
PAL_TRY {
... code which references locals from above
} PAL_EXCEPT_FILTER(ExceptionFilter) {
... code which references locals from above
} PAL_ENDTRY
and cause no additional code expansion in the Win32 build.
SEH Macros
PAL_TRY
- A direct replacement for Win32 "try". Note that the only way to exit from within a PAL_TRY/PAL_EXCEPT block is by either throwing an exception, or by using PAL_LEAVE. Exiting the block
using 'return' or 'goto' is not supported.
PAL_EXCEPT_FILTER(pfnFilter, pvParameter)
PAL_EXCEPT_FILTER_EX(LabelName, pfnFilter, pvParameter)
- Functions similarly to Win32 "except", but not entirely the same. The key
difference is that Win32 "except" takes an expression as its argument that,
essentially the compiler turns into a nested function called by the SEH exception dispatcher. The PAL version takes an actual pointer to a function
that acts as the exception filter. The pvParameter is passed verbatim to the exception filter routine - it can be used to store the addresses of local variables that would have been accessible in the nested filter function on Win32.
- The EX flavor has to be used in functions with multiple try/finally blocks. LabelName must be a unique name within the body of the C/C++ function containing multiple try/finally blocks.
PAL_EXCEPT (disposition)
PAL_EXCEPT_EX(LabelName, disposition)
- A shorthand for "except(disposition)". The valid values for disposition are EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH, and EXCEPTION_CONTINUE_EXECUTION.
PAL_FINALLY
PAL_FINALLY_EX(LabelName)
- Functions similarly to Win32 "finally". The LabelName must be a unique name within the body of the C/C++ function containing the try/finally. It is used to implement PAL_LEAVE
PAL_LEAVE
PAL_LEAVE_EX(LabelName)
- Functions similarly to Win32 "leave" as a way to exit a "try" block within a try/finally. The LabelName must match the name given to the corresponding, PAL_EXCEPT_FILTER, PAL_ FILTER or PAL_FINALLY.
PAL_ENDTRY
- No equivalent on Win32 - put this macro after the closing '}' at the end of the try/finally or try/except.
On UNIX system-based platforms, SEH is implemented by means of wrappers on top of setjmp/longjmp, so both SEH setup and exception dispatching will likely be larger and slower than x86 Win32 or even RISC Win32.
Note: use sparingly.
SEH for JIT-Compiled Code
Every entry into JIT-compiled code is enclosed in a try/catch block. The handler
associated with this try/catch block calls the appropriate JIT-compiled exception
handlers in case exception is thrown. Thus, the JIT-compiled code itself does not know
about the implementation details of exception handling (EH).
Unfortunately, the try/catch block around the JIT-compiled code cannot be the regular C/C++ SEH try block. It is the native EH record plugged directly into the fs:[0] chain in the Windows i386 version.
The main feature difference between the Win32 native EH and C/C++ SEH is in the stack state at the invocation of the body of
catch or finally clauses during the second pass:
- The C/C++ SEH unwinds the stack first and then calls the body of catch or finally clause.
- The native Win32 EH calls the handler with the stack unwound up to the *previous* EH record. It is up to the handler to unwind the stack up to itself.
It should be clear why it is enough for JIT-compiled code to be enclosed in the regular C/C++ SEH
try block: it will
not be possible to invoke the JIT-compiled second pass handlers because the stack they depend on would be gone.
It is also not a good idea to invoke both first and second pass JIT-compiled
handlers in the first pass of C/C++ SEH. The CLI depends on the
interop between the native and managed exceptions. The exception search often
flies through multiple floors of JIT-compiled and CLI code before the exception is
caught and the second pass starts. Invoking both first and second pass
JIT-compiled handlers in the first pass would change the order in which the
JIT-compiled handlers are executed.
Thus, it is necessary to emulate the Win32 native EH in addition to C/C++ SEH in the PAL. The differences between Win32 native EH and the C/C++ SEH are:
- dwFlags in PPAL_EXCEPTION_REGISTRATION is set to PAL_EXCEPTION_FLAGS_UNWINDONLY on PAL_TryEnter entry for Win32 native EH. It is 0 for C/C++ SEH.
- C/C++ SEH transfers the execution using longjmp in the second pass. Win32 native EH calls the handler function again instead: ExceptionRecord in PAL_EXCEPTION_POINTERS points to the exception information with
the EXCEPTION_UNWINDING bit set on dwFlags. ContextRecord in PAL_EXCEPTION_POINTERS is NULL.
- The bottommost registration is the registration preceding the active registration on entry to the
catch or finally blocks in C/C++ SEH. The bottommost registration is the active registration on call to the handler in Win32 EH.
- The second pass call to the handler function in Win32 native SEH returns
EXCEPTION_CONTINUE_SEARCH or never returns (for example, when JIT-compiled code handled the exception and continues running).
- Win32 native EH frames and C/C++ SEH can be mixed with no limitations.
SEH Implementation
The following routines must be implemented by PALs on non-Win32 platforms:
PAL_TryHelper
- On entry, the PPAL_EXCEPTION_REGISTRATION record will have the pfnFilter, pvFilterParameter and dwFlags fields filled in. The other fields will be uninitialized.
- The ReservedForPAL[] field may be used for storage of implementation-specific data. It is intended as a C runtime jmp_buf.
- The return value should be 0 on the initial call, to indicate that the SEH
handler has been registered. The routine may modify any fields within the
PPAL_EXCEPTION_REGISTRATION. The caller treats the record as opaque after the call. If an exception happens subsequently, control should resume within PAL_TryHelper and the return value should be the exception code.
- Note: there is no error code. The PAL may not fail the call due to
low-memory, and so on.
PAL_EndTryHelper
- On entry, the PPAL_EXCEPTION_REGISTRATION record will be the one returned from the previous PAL_TryHelper call. It would be helpful for the PAL to carefully check that PAL_TryHelper and PAL_EndTryHelper calls are matched - if an errant PAL_LEAVE macro or other code flow construct does a "leave" to the wrong "try", it
might corrupt the stack.
PAL_GetBottommostRegistration
- Returns the PPAL_EXCEPTION_REGISTRATION record that is bottommost on the stack. The API will always return non-NULL, assuming that the PAL has placed one of these at the top of the stack before calling the thread entry point.
PAL_SetBottommostRegistration
- Sets the PPAL_EXCEPTION_REGISTRATION record that is bottommost on the stack.
PAL_GetBottommostRegistrationPtr
- Returns PPAL_EXCEPTION_REGISTRATION * where this is the address of the bottommost PPAL_EXCEPTION_REGISTRATION stored for the current thread.
Localized Resource Strings
Several components within the common language runtime use LoadLibrary and then LoadString to fetch localizable text resources from satellite DLLs. Rather than duplicating this PE/COFF resource structure within a
UNIX system-style shared-library, a better solution is to switch to a generic text-based system. This solution is implemented in the PAL
runtime, on top of the PAL APIs, so it can be shared among all platforms. However, it is documented here, as it may be useful in the PAL's implementation of FormatMessage.
LoadLibrary/LoadLibraryEx calls used to load satellite DLLs will be replaced by:
typedef HANDLE HSATELLITE;
PALIMPORT
HSATELLITE
PALAPI
PAL_LoadSatelliteResourceW(LPCWSTR SatelliteResourceFileName);
#ifdef UNICODE
#define LoadSatelliteResource LoadSatelliteResourceW
#endif
LoadString calls to fetch strings from the satellite DLL will be replaced by:
PALIMPORT
int
PALAPI
PAL_LoadSatelliteStringW(HSATELLITE SatelliteResource,
UINT uID,
LPWSTR lpBuffer,
int nBufferMax);
PALIMPORT
int
PALAPI
PAL_LoadSatelliteStringA(HSATELLITE SatelliteResource,
UINT uID,
LPSTR lpBuffer,
int nBufferMax);
#ifdef UNICODE
#define PAL_LoadSatelliteResource PAL_LoadSatelliteResourceW
#else
#define PAL_LoadSatelliteResource PAL_LoadSatelliteResourceA
#endif
FreeLibrary calls to release satellite DLLs will be replaced by:
PALIMPORT
BOOL
PALAPI
PAL_FreeSatelliteResource(HSATELLITE SatelliteResource);
The format of the satellite DLL will be a UTF-8 encoded file containing a
list of pairs of decimal numbers and quoted string pairs, with a whitespace
between. The maximum string length is 511 bytes, not including the required
quotes. For example, the following three messages are valid:
1 "This is a message"
2 "This is a message"
39 "This is
another message"
But the following are not valid:
A "This is a message"
1 2
The LoadSatelliteString APIs will scan through the table by reading in each message number and quoted string pair, one at a time until the desired message number is found or EOF is hit. If an
invalid message or string pair is found, then the APIs should immediately return failure. Note:
Checks for duplicated message
IDs are not required; the API behavior is undefined.
The satellite message files will be generated using a tool,
resourcecompiler, which
functions like the Win32 Resource compiler, converting STRINGTABLE resources in
the .rc file into the new satellite message file format. The conversion tool
should check that no duplicated message IDs are present, and either emit a file
containing all legal ID/string pairs, or error out. The SSCLI includes an implementation
of the new resource compiler tool that produces the satellite files. Note that resource IDs in both the source .rc file and as parameters to LoadSatelliteResource are both truncated to 16-bit values, for compatibility with Win32 resource IDs.
The satellite resource data will be reference-counted; multiple calls to LoadSatelliteResource() to load the same resource file name are permitted, and each call must have a matching FreeSatelliteResource() call. Satellite resource files are considered to be the same only if their fully qualified path names are identical.
Note:
The file or path name passed to LoadSatelliteResource() may be a partial path that must be fully qualified by the PAL implementation.
It would be useful to reuse the managed resource format (System.Resources.ResourceManager), but this will add a significant bootstrapping dependency since so many unmanaged binaries depend on these for core functionality.
This routine will be implemented within the core URT tree, not within the PAL. It can easily be built on top of the PAL APIs.
Security
The PAL must avoid introducing security holes that can be exploited from managed code. In particular, the sequence of
CreateFile/ReadFile/CreateFileMapping/MapViewOfFile must guarantee that if the file was opened with deny-write, that exactly the same data is read from the file
through both ReadFile and MapViewOfFile. In other words, CreateFileMapping or
MapViewOfFile cannot be implemented using an additional file-open API call
that uses the file name, because this could potentially result in opening a different file
because another process could rename the first file and substitute a new file with the name of the first file.
The PAL must also avoid buffer overruns when dealing with strings such as file and directory names.
DLLMain
LoadLibrary will call DllMain if there is one on PROCESS_ATTACH / PROCESS_DETACH / THREAD_ATTACH / THREAD_DETACH. DisableThreadLibraryCalls will work as usual. The Win32 DllMain thread safety invariant (only one DllMain is called on one thread at a time) holds.
If DLL is statically linked to other PAL DLLs, it has to call PAL_RegisterLibrary for all DLLs it depends on in its PROCESS_ATTACH handler and PAL_UnregisterLibrary in its PROCESS_DETACH handler.
In the UNIX system-based platform PAL implementation, PAL_Register/PAL_Unregister calls are identical to LoadLibrary / FreeLibrary. In the Win32 PAL implementation, these calls are
NO-OPs.
Calls to DllMain from LoadLibrary should be protected against cyclic references between DLLs.
This solution minimizes changes in the Win32 code. The only change necessary is
a couple of calls to PAL_RegisterLibrary/PAL_UnregisterLibrary under #ifdef FEATURE_PAL. It has also a very simple implementation in the Win32 PAL.
Unexpected Termination
In some circumstances, the PAL might acquire and use resources shared between several cooperating processes. If the process running the PAL terminates or blocks unexpectedly due to an asynchronous event, and the PAL is currently executing code within a PAL API, then the state of the shared resources
might be inconsistent.
For example, if the PAL is currently executing CreateEvent() and the event is named, and at the precise moment that the event name is being copied into memory shared between all PAL processes, the user kills the process with 'kill -f', then the shared memory
might be locked forever, or the contents might contain only a fragment of the event's name. In this case, it is acceptable for other PAL processes to block forever or crash, if they access the PAL's shared memory.
The PAL should not acquire and hold locks when no PAL APIs are executing, and should not hold locks across long-running APIs such as WaitForSingleObject().
An alternative solution to this problem is to create a common, trusted, helper process to store the shared state; PAL client processes would request reads and writes
using LRPC calls. The server could be robust against errant clients, and reduce the points of failure down to just the trusted helper.
Appendix A: Building rotor_pal.satellite
The current implementation of the Shared Source CLI provides tools
support for generating a rotor_pal.satellite file which is a string resource
file as described above in Localized String Resources.
In %ROTOR_DIR%\pal\unix, there is a rotor_pal.satellite file, which contains
the mapping from Win32 LastError codes to English strings. This mapping is used by the
PAL implementation of FormatMessage. The rotor_pal.satellite is
computer-generated by building tools\palsatellite on a computer running Windows, and
executing it while the current directory is set to %ROTOR_DIR%\pal:
- cd %ROTOR_DIR%\tools\palsatellite
- build
- cd %ROTOR_DIR%\pal
- %ROTOR_DIR%\tools\palsatellite\rotor_x86\palsatellite.exe
This will create a new rotor_pal.satellite file in %ROTOR_DIR%\pal, which
needs to be manually moved to %ROTOR_DIR%\pal\unix.
The tool works by scanning rotor_pal.h for lines starting with "#define ERROR_".
The tool captures the error number constant, then calls Win32 FormatMessage,
captures the output, and then formats it into a SSCLI satellite file.
After running the tool, the LastError mapping prints exactly the same messages
on both the Windows and UNIX system-based SSCLI PAL implementation.
This tool needs to be re-run whenever new ERROR_ constants are added to
rotor_pal.h. They can be a cut and pasted from the Win32 Platform SDK file,
winerror.h.
Copyright (c) 2002 Microsoft Corporation. All rights reserved.