/*++ Copyright (c) 1990 Microsoft Corporation Module Name: debug.c Abstract: This module implements Win32 Debug APIs Author: Mark Lucovsky (markl) 06-Feb-1991 Revision History: --*/ #include "basedll.h" #pragma hdrstop #include "ntdbg.h" BOOL APIENTRY IsDebuggerPresent( VOID ) /*++ Routine Description: This function returns TRUE if the current process is being debugged and FALSE if not. Arguments: None. Return Value: None. --*/ { return NtCurrentPeb()->BeingDebugged; } //#ifdef i386 //#pragma optimize("",off) //#endif // i386 VOID APIENTRY DebugBreak( VOID ) /*++ Routine Description: This function causes a breakpoint exception to occur in the caller. This allows the calling thread to signal the debugger forcing it to take some action. If the process is not being debugged, the standard exception search logic is invoked. In most cases, this will cause the calling process to terminate (due to an unhandled breakpoint exception). Arguments: None. Return Value: None. --*/ { DbgBreakPoint(); } //#ifdef i386 //#pragma optimize("",on) //#endif // i386 VOID APIENTRY OutputDebugStringW( LPCWSTR lpOutputString ) /*++ Routine Description: UNICODE thunk to OutputDebugStringA --*/ { UNICODE_STRING UnicodeString; ANSI_STRING AnsiString; NTSTATUS Status; RtlInitUnicodeString(&UnicodeString,lpOutputString); Status = RtlUnicodeStringToAnsiString(&AnsiString,&UnicodeString,TRUE); if ( !NT_SUCCESS(Status) ) { AnsiString.Buffer = ""; } OutputDebugStringA(AnsiString.Buffer); if ( NT_SUCCESS(Status) ) { RtlFreeAnsiString(&AnsiString); } } #define DBWIN_TIMEOUT 10000 VOID APIENTRY OutputDebugStringA( IN LPCSTR lpOutputString ) /*++ Routine Description: This function allows an application to send a string to its debugger for display. If the application is not being debugged, but the system debugger is active, the system debugger displays the string. Otherwise, this function has no effect. Arguments: lpOutputString - Supplies the address of the debug string to be sent to the debugger. Return Value: None. --*/ { DWORD ExceptionArguments[2]; // // Raise an exception. If APP is being debugged, the debugger // will catch and handle this. Otherwise, kernel debugger is // called. // try { ExceptionArguments[0]=strlen(lpOutputString)+1; ExceptionArguments[1]=(DWORD)lpOutputString; RaiseException(DBG_PRINTEXCEPTION_C,0,2,ExceptionArguments); } except(EXCEPTION_EXECUTE_HANDLER) { // // We caught the debug exception, so there's no user-mode // debugger. If there is a DBWIN running, send the string // to it. If not, use DbgPrint to send it to the kernel // debugger. DbgPrint can only handle 511 characters at a // time, so force-feed it. // char szBuf[512]; size_t cchRemaining; LPCSTR pszRemainingOutput; HANDLE SharedFile = NULL; LPSTR SharedMem = NULL; HANDLE AckEvent = NULL; HANDLE ReadyEvent = NULL; static HANDLE DBWinMutex = NULL; // // look for DBWIN. // if (!DBWinMutex) { SECURITY_ATTRIBUTES SecurityAttributes; SECURITY_DESCRIPTOR SecurityDescriptor; PSECURITY_ATTRIBUTES pSecurityAttributes = NULL; NTSTATUS Status; // // Create a descriptor with a NULL DACL instead of letting // it use the default. // SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); SecurityAttributes.bInheritHandle = TRUE; SecurityAttributes.lpSecurityDescriptor = &SecurityDescriptor; Status = RtlCreateSecurityDescriptor ( &SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION ); if (NT_SUCCESS(Status)) { Status = RtlSetDaclSecurityDescriptor ( &SecurityDescriptor, TRUE, NULL, FALSE ); if (NT_SUCCESS(Status)) { pSecurityAttributes = &SecurityAttributes; } } DBWinMutex = CreateMutex(pSecurityAttributes, FALSE, "DBWinMutex"); } if (DBWinMutex) { WaitForSingleObject(DBWinMutex, INFINITE); SharedFile = OpenFileMapping(FILE_MAP_WRITE, FALSE, "DBWIN_BUFFER"); if (SharedFile) { SharedMem = MapViewOfFile( SharedFile, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0); if (SharedMem) { AckEvent = OpenEvent(SYNCHRONIZE, FALSE, "DBWIN_BUFFER_READY"); if (AckEvent) { ReadyEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, "DBWIN_DATA_READY"); } } } if (!ReadyEvent) { ReleaseMutex(DBWinMutex); } } try { pszRemainingOutput = lpOutputString; cchRemaining = strlen(pszRemainingOutput); while (cchRemaining > 0) { int used; if (ReadyEvent && WaitForSingleObject(AckEvent, DBWIN_TIMEOUT) == WAIT_OBJECT_0) { *((DWORD *)SharedMem) = GetCurrentProcessId(); used = (cchRemaining < 4095 - sizeof(DWORD)) ? cchRemaining : (4095 - sizeof(DWORD)); RtlCopyMemory(SharedMem+sizeof(DWORD), pszRemainingOutput, used); SharedMem[used+sizeof(DWORD)] = 0; SetEvent(ReadyEvent); } else { used = (cchRemaining < sizeof(szBuf) - 1) ? cchRemaining : (sizeof(szBuf) - 1); RtlCopyMemory(szBuf, pszRemainingOutput, used); szBuf[used] = 0; DbgPrint("%s", szBuf); } pszRemainingOutput += used; cchRemaining -= used; } } except(STATUS_ACCESS_VIOLATION == GetExceptionCode()) { DbgPrint("\nOutputDebugString faulted during output\n"); } if (AckEvent) { CloseHandle(AckEvent); } if (SharedMem) { UnmapViewOfFile(SharedMem); } if (SharedFile) { CloseHandle(SharedFile); } if (ReadyEvent) { CloseHandle(ReadyEvent); ReleaseMutex(DBWinMutex); } } } BOOL APIENTRY WaitForDebugEvent( LPDEBUG_EVENT lpDebugEvent, DWORD dwMilliseconds ) /*++ Routine Description: A debugger waits for a debug event to occur in one of its debuggees using WaitForDebugEvent: Upon successful completion of this API, the lpDebugEvent structure contains the relevant information of the debug event. Arguments: lpDebugEvent - Receives information specifying the type of debug event that occured. dwMilliseconds - A time-out value that specifies the relative time, in milliseconds, over which the wait is to be completed. A timeout value of 0 specified that the wait is to timeout immediately. This allows an application to test for debug events A timeout value of -1 specifies an infinite timeout period. Return Value: TRUE - The operation was successful. FALSE/NULL - The operation failed (or timed out). Extended error status is available using GetLastError. --*/ { NTSTATUS Status; DBGUI_WAIT_STATE_CHANGE StateChange; LARGE_INTEGER TimeOut; PLARGE_INTEGER pTimeOut; THREAD_BASIC_INFORMATION ThreadBasicInfo; HANDLE hThread; OBJECT_ATTRIBUTES Obja; pTimeOut = BaseFormatTimeOut(&TimeOut,dwMilliseconds); again: Status = DbgUiWaitStateChange(&StateChange,pTimeOut); if ( Status == STATUS_ALERTED || Status == STATUS_USER_APC) { goto again; } if ( !NT_SUCCESS(Status) && Status != DBG_UNABLE_TO_PROVIDE_HANDLE ) { BaseSetLastNTError(Status); return FALSE; } if ( Status == STATUS_TIMEOUT ) { SetLastError(ERROR_SEM_TIMEOUT); return FALSE; } lpDebugEvent->dwProcessId = (DWORD)StateChange.AppClientId.UniqueProcess; lpDebugEvent->dwThreadId = (DWORD)StateChange.AppClientId.UniqueThread; switch ( StateChange.NewState ) { case DbgCreateThreadStateChange : lpDebugEvent->dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT; lpDebugEvent->u.CreateThread.hThread = StateChange.StateInfo.CreateThread.HandleToThread; lpDebugEvent->u.CreateThread.lpStartAddress = (LPTHREAD_START_ROUTINE)StateChange.StateInfo.CreateThread.NewThread.StartAddress; Status = NtQueryInformationThread( StateChange.StateInfo.CreateThread.HandleToThread, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), NULL ); if (!NT_SUCCESS(Status)) { lpDebugEvent->u.CreateThread.lpThreadLocalBase = NULL; } else { lpDebugEvent->u.CreateThread.lpThreadLocalBase = ThreadBasicInfo.TebBaseAddress; } break; case DbgCreateProcessStateChange : lpDebugEvent->dwDebugEventCode = CREATE_PROCESS_DEBUG_EVENT; lpDebugEvent->u.CreateProcessInfo.hProcess = StateChange.StateInfo.CreateProcessInfo.HandleToProcess; lpDebugEvent->u.CreateProcessInfo.hThread = StateChange.StateInfo.CreateProcessInfo.HandleToThread; lpDebugEvent->u.CreateProcessInfo.hFile = StateChange.StateInfo.CreateProcessInfo.NewProcess.FileHandle; lpDebugEvent->u.CreateProcessInfo.lpBaseOfImage = StateChange.StateInfo.CreateProcessInfo.NewProcess.BaseOfImage; lpDebugEvent->u.CreateProcessInfo.dwDebugInfoFileOffset = StateChange.StateInfo.CreateProcessInfo.NewProcess.DebugInfoFileOffset; lpDebugEvent->u.CreateProcessInfo.nDebugInfoSize = StateChange.StateInfo.CreateProcessInfo.NewProcess.DebugInfoSize; lpDebugEvent->u.CreateProcessInfo.lpStartAddress = (LPTHREAD_START_ROUTINE)StateChange.StateInfo.CreateProcessInfo.NewProcess.InitialThread.StartAddress; Status = NtQueryInformationThread( StateChange.StateInfo.CreateProcessInfo.HandleToThread, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), NULL ); if (!NT_SUCCESS(Status)) { lpDebugEvent->u.CreateProcessInfo.lpThreadLocalBase = NULL; } else { lpDebugEvent->u.CreateProcessInfo.lpThreadLocalBase = ThreadBasicInfo.TebBaseAddress; } lpDebugEvent->u.CreateProcessInfo.lpImageName = NULL; lpDebugEvent->u.CreateProcessInfo.fUnicode = 1; break; case DbgExitThreadStateChange : lpDebugEvent->dwDebugEventCode = EXIT_THREAD_DEBUG_EVENT; lpDebugEvent->u.ExitThread.dwExitCode = (DWORD)StateChange.StateInfo.ExitThread.ExitStatus; break; case DbgExitProcessStateChange : lpDebugEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT; lpDebugEvent->u.ExitProcess.dwExitCode = (DWORD)StateChange.StateInfo.ExitProcess.ExitStatus; break; case DbgExceptionStateChange : case DbgBreakpointStateChange : case DbgSingleStepStateChange : if ( StateChange.StateInfo.Exception.ExceptionRecord.ExceptionCode == DBG_PRINTEXCEPTION_C ) { lpDebugEvent->dwDebugEventCode = OUTPUT_DEBUG_STRING_EVENT; lpDebugEvent->u.DebugString.lpDebugStringData = (PVOID)StateChange.StateInfo.Exception.ExceptionRecord.ExceptionInformation[1]; lpDebugEvent->u.DebugString.nDebugStringLength = (WORD)StateChange.StateInfo.Exception.ExceptionRecord.ExceptionInformation[0]; lpDebugEvent->u.DebugString.fUnicode = (WORD)0; } else if ( StateChange.StateInfo.Exception.ExceptionRecord.ExceptionCode == DBG_RIPEXCEPTION ) { lpDebugEvent->dwDebugEventCode = RIP_EVENT; lpDebugEvent->u.RipInfo.dwType = (DWORD)StateChange.StateInfo.Exception.ExceptionRecord.ExceptionInformation[1]; lpDebugEvent->u.RipInfo.dwError = (DWORD)StateChange.StateInfo.Exception.ExceptionRecord.ExceptionInformation[0]; } else { lpDebugEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT; lpDebugEvent->u.Exception.ExceptionRecord = StateChange.StateInfo.Exception.ExceptionRecord; lpDebugEvent->u.Exception.dwFirstChance = StateChange.StateInfo.Exception.FirstChance; } break; case DbgLoadDllStateChange : lpDebugEvent->dwDebugEventCode = LOAD_DLL_DEBUG_EVENT; lpDebugEvent->u.LoadDll.lpBaseOfDll = StateChange.StateInfo.LoadDll.BaseOfDll; lpDebugEvent->u.LoadDll.hFile = StateChange.StateInfo.LoadDll.FileHandle; lpDebugEvent->u.LoadDll.dwDebugInfoFileOffset = StateChange.StateInfo.LoadDll.DebugInfoFileOffset; lpDebugEvent->u.LoadDll.nDebugInfoSize = StateChange.StateInfo.LoadDll.DebugInfoSize; { // // pick up the image name // InitializeObjectAttributes(&Obja, NULL, 0, NULL, NULL); Status = NtOpenThread( &hThread, THREAD_QUERY_INFORMATION, &Obja, &StateChange.AppClientId ); if ( NT_SUCCESS(Status) ) { Status = NtQueryInformationThread( hThread, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), NULL ); NtClose(hThread); } if ( NT_SUCCESS(Status) ) { lpDebugEvent->u.LoadDll.lpImageName = &ThreadBasicInfo.TebBaseAddress->NtTib.ArbitraryUserPointer; } else { lpDebugEvent->u.LoadDll.lpImageName = NULL; } lpDebugEvent->u.LoadDll.fUnicode = 1; } break; case DbgUnloadDllStateChange : lpDebugEvent->dwDebugEventCode = UNLOAD_DLL_DEBUG_EVENT; lpDebugEvent->u.UnloadDll.lpBaseOfDll = StateChange.StateInfo.UnloadDll.BaseAddress; break; default: return FALSE; } return TRUE; } BOOL APIENTRY ContinueDebugEvent( DWORD dwProcessId, DWORD dwThreadId, DWORD dwContinueStatus ) /*++ Routine Description: A debugger can continue a thread that previously reported a debug event using ContinueDebugEvent. Upon successful completion of this API, the specified thread is continued. Depending on the debug event previously reported by the thread certain side effects occur. If the continued thread previously reported an exit thread debug event, the handle that the debugger has to the thread is closed. If the continued thread previously reported an exit process debug event, the handles that the debugger has to the thread and to the process are closed. Arguments: dwProcessId - Supplies the process id of the process to continue. The combination of process id and thread id must identify a thread that has previously reported a debug event. dwThreadId - Supplies the thread id of the thread to continue. The combination of process id and thread id must identify a thread that has previously reported a debug event. dwContinueStatus - Supplies the continuation status for the thread reporting the debug event. dwContinueStatus Values: DBG_CONTINUE - If the thread being continued had previously reported an exception event, continuing with this value causes all exception processing to stop and the thread continues execution. For any other debug event, this continuation status simply allows the thread to continue execution. DBG_EXCEPTION_NOT_HANDLED - If the thread being continued had previously reported an exception event, continuing with this value causes exception processing to continue. If this is a first chance exception event, then structured exception handler search/dispatch logic is invoked. Otherwise, the process is terminated. For any other debug event, this continuation status simply allows the thread to continue execution. DBG_TERMINATE_THREAD - After all continue side effects are processed, this continuation status causes the thread to jump to a call to ExitThread. The exit code is the value DBG_TERMINATE_THREAD. DBG_TERMINATE_PROCESS - After all continue side effects are processed, this continuation status causes the thread to jump to a call to ExitProcess. The exit code is the value DBG_TERMINATE_PROCESS. Return Value: TRUE - The operation was successful. FALSE/NULL - The operation failed. Extended error status is available using GetLastError. --*/ { NTSTATUS Status; CLIENT_ID ClientId; ClientId.UniqueProcess = (HANDLE)dwProcessId; ClientId.UniqueThread = (HANDLE)dwThreadId; Status = DbgUiContinue(&ClientId,(NTSTATUS)dwContinueStatus); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); return FALSE; } return TRUE; } BOOL APIENTRY DebugActiveProcess( DWORD dwProcessId ) /*++ Routine Description: This API allows a debugger to attach to an active process and debug the process. The debugger specifies the process that it wants to debug through the process id of the target process. The debugger gets debug access to the process as if it had created the process with the DEBUG_ONLY_THIS_PROCESS creation flag. The debugger must have approriate access to the calling process such that it can open the process for PROCESS_ALL_ACCESS. For Dos/Win32 this never fails (the process id just has to be a valid process id). For NT/Win32 this check can fail if the target process was created with a security descriptor that denies the debugger approriate access. Once the process id check has been made and the system determines that a valid debug attachment is being made, this call returns success to the debugger. The debugger is then expected to wait for debug events. The system will suspend all threads in the process and feed the debugger debug events representing the current state of the process. The system will feed the debugger a single create process debug event representing the process specified by dwProcessId. The lpStartAddress field of the create process debug event is NULL. For each thread currently part of the process, the system will send a create thread debug event. The lpStartAddress field of the create thread debug event is NULL. For each DLL currently loaded into the address space of the target process, the system will send a LoadDll debug event. The system will arrange for the first thread in the process to execute a breakpoint instruction after it is resumed. Continuing this thread causes the thread to return to whatever it was doing prior to the debug attach. After all of this has been done, the system resumes all threads within the process. When the first thread in the process resumes, it will execute a breakpoint instruction causing an exception debug event to be sent to the debugger. All future debug events are sent to the debugger using the normal mechanism and rules. Arguments: dwProcessId - Supplies the process id of a process the caller wants to debug. Return Value: TRUE - The operation was successful. FALSE/NULL - The operation failed. Extended error status is available using GetLastError. --*/ { HANDLE Process, Thread; NTSTATUS Status; DWORD ThreadId; PBASE_API_MSG m; PBASE_DEBUGPROCESS_MSG a; // // Determine if a valid process id has been specified. If // so than call the server to do the attach. // if ( dwProcessId != -1 ) { Process = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessId); if ( !Process ) { return FALSE; } // // Call server to see if we can really debug this process // { BASE_API_MSG mm; PBASE_DEBUGPROCESS_MSG aa= (PBASE_DEBUGPROCESS_MSG)&mm.u.DebugProcess; aa->DebuggerClientId = NtCurrentTeb()->ClientId; aa->dwProcessId = dwProcessId; aa->AttachCompleteRoutine = NULL; CsrClientCallServer( (PCSR_API_MSG)&mm, NULL, CSR_MAKE_API_NUMBER( BASESRV_SERVERDLL_INDEX, BasepDebugProcess ), sizeof( BASE_DEBUGPROCESS_MSG ) ); if (!NT_SUCCESS((NTSTATUS)mm.ReturnValue)) { SetLastError(ERROR_ACCESS_DENIED); CloseHandle(Process); return FALSE; } } CloseHandle(Process); } else { // // Call server to see if we can really debug this process // { BASE_API_MSG mm; PBASE_DEBUGPROCESS_MSG aa= (PBASE_DEBUGPROCESS_MSG)&mm.u.DebugProcess; aa->DebuggerClientId = NtCurrentTeb()->ClientId; aa->dwProcessId = dwProcessId; aa->AttachCompleteRoutine = NULL; CsrClientCallServer( (PCSR_API_MSG)&mm, NULL, CSR_MAKE_API_NUMBER( BASESRV_SERVERDLL_INDEX, BasepDebugProcess ), sizeof( BASE_DEBUGPROCESS_MSG ) ); if (!NT_SUCCESS((NTSTATUS)mm.ReturnValue)) { SetLastError(ERROR_ACCESS_DENIED); return FALSE; } } } // // Connect to dbgss as a user interface // Status = DbgUiConnectToDbg(); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); return FALSE; } m = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), sizeof(*m)); if ( !m ) { BaseSetLastNTError(STATUS_NO_MEMORY); return FALSE; } a = (PBASE_DEBUGPROCESS_MSG)&m->u.DebugProcess; a->DebuggerClientId = NtCurrentTeb()->ClientId; a->dwProcessId = dwProcessId; a->AttachCompleteRoutine = (PVOID)BaseAttachComplete; Thread = CreateThread( NULL, 0L, BaseDebugAttachThread, (LPVOID)m, 0, &ThreadId ); if ( !Thread ) { RtlFreeHeap(RtlProcessHeap(), 0,m); return FALSE; } CloseHandle(Thread); return TRUE; } DWORD BaseDebugAttachThread( LPVOID ThreadParameter ) /*++ Routine Description: This thread is created as part of the debug attach procedure. It runs in the context of the attaching debugger. Its basic function is to call the server and block until the server completes the attachment. It then exits. This thread is needed because the debugger making the attach call must be free to service debug events from the server. Rather than have the server have added complexity of doing the debug attach procedure asynchronously, we grab a debugger thread to block. Arguments: ThreadParameter - Supplies the address of the base api message Not used. Return Value: None. --*/ { PBASE_API_MSG m; NTSTATUS Status; ULONG Response; m = (PBASE_API_MSG)ThreadParameter; CsrClientCallServer( (PCSR_API_MSG)ThreadParameter, NULL, CSR_MAKE_API_NUMBER( BASESRV_SERVERDLL_INDEX, BasepDebugProcess ), sizeof( BASE_DEBUGPROCESS_MSG ) ); if (!NT_SUCCESS((NTSTATUS)m->ReturnValue)) { // // Unexpected attach failure // Status =NtRaiseHardError( STATUS_DEBUG_ATTACH_FAILED | 0x10000000, 0, 0, NULL, OptionOkCancel, &Response ); if ( NT_SUCCESS(Status) && Response == ResponseOk ) { ExitProcess(Status); } } RtlFreeHeap(RtlProcessHeap(), 0,ThreadParameter); ExitThread((DWORD)0); return 0; } VOID BaseAttachComplete( PCONTEXT Context ) /*++ Routine Description: This function is remote called to after a successful debug attach. Its purpose is to issue a breakpoint and the continue. Arguments: Context - Supplies the context record that is to be restored upon completion of this API. Return Value: None. --*/ { HANDLE DebugPort; NTSTATUS Status; DebugPort = (HANDLE)NULL; Status = NtQueryInformationProcess( NtCurrentProcess(), ProcessDebugPort, (PVOID)&DebugPort, sizeof(DebugPort), NULL ); if ( NT_SUCCESS(Status) && DebugPort ) { DbgBreakPoint(); } if ( !Context ) { ExitThread(0); ASSERT(FALSE); } else { NtContinue(Context,FALSE); } } BOOL APIENTRY GetThreadSelectorEntry( HANDLE hThread, DWORD dwSelector, LPLDT_ENTRY lpSelectorEntry ) /*++ Routine Description: This function is used to return a descriptor table entry for the specified thread corresponding to the specified selector. This API is only functional on x86 based systems. For non x86 based systems. A value of FALSE is returned. This API is used by a debugger so that it can convert segment relative addresses to linear virtual address (since this is the only format supported by ReadProcessMemory and WriteProcessMemory. Arguments: hThread - Supplies a handle to the thread that contains the specified selector. The handle must have been created with THREAD_QUERY_INFORMATION access. dwSelector - Supplies the selector value to lookup. The selector value may be a global selector or a local selector. lpSelectorEntry - If the specified selector is contained withing the threads descriptor tables, this parameter returns the selector entry corresponding to the specified selector value. This data can be used to compute the linear base address that segment relative addresses refer to. Return Value: TRUE - The operation was successful FALSE/NULL - The operation failed. Extended error status is available using GetLastError. --*/ { #ifdef i386 DESCRIPTOR_TABLE_ENTRY DescriptorEntry; NTSTATUS Status; DescriptorEntry.Selector = dwSelector; Status = NtQueryInformationThread( hThread, ThreadDescriptorTableEntry, &DescriptorEntry, sizeof(DescriptorEntry), NULL ); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); return FALSE; } *lpSelectorEntry = DescriptorEntry.Descriptor; return TRUE; #else BaseSetLastNTError(STATUS_NOT_SUPPORTED); return FALSE; #endif // i386 }