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:

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

Memory Requirements

Threading Requirements

Miscellaneous Operating System Requirements

Furthermore, the host operating system must support the following:

Compilation Environment

The compilation environment requires:

Basic Requirements

The following are basic requirements of the PAL:

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:

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:

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.

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. 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

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

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

wsprintfW

The format string must support %x, %s, %d, and other C runtime standard printf formatters, plus %ws.

wsprintfA

MessageBoxW

hWnd - always NULL.

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:

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.

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)

CreateDirectoryA CreateDirectoryW

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)

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

GetStdHandle

The handles returned from GetStdHandle may be passed to CreateProcess in the STARTUPINFO structure.

SetEndOfFile

SetFilePointer

ReadFile

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

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

GetTempPathA GetTempPathW

GetCurrentDirectoryA GetCurrentDirectoryW

SetCurrentDirectoryA SetCurrentDirectoryW

LockFile

UnlockFile

GetDiskFreeSpaceW

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

Threads and Processes

CreateSemaphoreA CreateSemaphoreW ReleaseSemaphore

CreateEventA CreateEventW

SetEvent

ResetEvent

OpenEventW (OpenEventA not required)

CreateMutexW (CreateMutexA not required)

ReleaseMutex

GetCurrentProcessId

CreateProcessA CreateProcessW

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

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

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

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

ResumeThread

QueueUserAPC

GetThreadContext

SetThreadContext

GetCurrentThread

GetThreadPriority

SetThreadPriority

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

ExitThread

GetProcessTimes

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

The PAL may implement this function as a NO-OP, ignoring the input parameter, and always returning 0.

ExitProcess

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

This API is used by cordbg's "attach" command, to attach the debugger to the specified process.

Memory Management

CreateFileMappingA CreateFileMappingW

OpenFileMappingA OpenFileMappingW

MapViewOfFile

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

VirtualAlloc

VirtualFree

VirtualProtect

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

GetProcessHeap

This can return any value - the return value is passed to HeapAlloc() or HeapFree() only as a cookie.

HeapAlloc

HeapReAlloc

HeapFree

LocalAlloc

LocalFree

FlushInstructionCache

The range of memory specified by lpBaseAddress + dwSize must be committed.

RtlMoveMemory

CopyMemory

Locale Information

GetStringTypeExW

CompareStringW

GetLocaleInfoW

GetACP

GetCPInfo

IsValidCodePage

WideCharToMultiByte

MultiByteToWideChar

GetSystemDefaultLangID

GetUserDefaultLangID

SetThreadLocale

GetThreadLocale

GetUserDefaultLCID

GetTimeZoneInformation

IsValidLocale

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

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

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 PAL cannot determine whether a native debugger is present then the following is applicable:

DebugBreak

lstrcatW

lstrcpyW

lstrlenA lstrlenW

lstrcpynW

GetEnvironmentVariableA GetEnvironmentVariableW

SetEnvironmentVariableA SetEnvironmentVariableW

CloseHandle

RaiseException

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)

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

SetConsoleCtrlHandler

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

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

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

swscanf

Scan string may be "%[^,],%[^,],%s" or "%u%n" or "%[^,],%[^,],%[^,],%c,%c".

time

localtime

mktime

_gcvt

_open_osfhandle

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

_wfopen

Wsock32.dll, Ws2_32.dll

gethostbyname

gethostbyaddr

gethostname

inet_addr

getpeername

getsockopt

getsockopt must support the following level/optname combinations: 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

setsockopt must support the following level/optname combinations: 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 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

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

WSACleanup

WSASend

WSASendTo

WSARecv

WSARecvFrom

WSAEventSelect

WSASocketA

WSAIoctl

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

ioctlsocket

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:

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: 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:

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

PAL_EXCEPT_FILTER(pfnFilter, pvParameter)

PAL_EXCEPT_FILTER_EX(LabelName, pfnFilter, pvParameter)

PAL_EXCEPT (disposition)

PAL_EXCEPT_EX(LabelName, disposition)

PAL_FINALLY

PAL_FINALLY_EX(LabelName)

PAL_LEAVE

PAL_LEAVE_EX(LabelName)

PAL_ENDTRY

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:

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:

SEH Implementation

The following routines must be implemented by PALs on non-Win32 platforms:

PAL_TryHelper

PAL_EndTryHelper

PAL_GetBottommostRegistration

PAL_SetBottommostRegistration

PAL_GetBottommostRegistrationPtr

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:

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.