/*++ Copyright (c) 1993 Microsoft Corporation Module Name: dmx32.c Author: Wesley Witt (wesw) 15-Aug-1992 Environment: NT 3.1 --*/ #include "precomp.h" #pragma hdrstop #include "simpldis.h" #include "dmsql.h" #include "fiber.h" DBF *lpdbf; #undef LOCAL typedef enum { Image_Unknown, Image_16, Image_32, Image_Dump } IMAGETYPE; typedef IMAGETYPE *PIMAGETYPE; enum { System_DmpInvalid = -3, /* The DUMP can not be debugged */ System_DmpWrongPlatform = -2, /* The cross-platform debugging */ /* of DUMPs is not supported */ System_Invalid = -1, /* The exe can not be debugged */ System_Console = 1, /* The exe needs a console */ System_GUI = 0 /* The exe is a Windows exe */ }; int pCharMode(LPTSTR szAppName, PIMAGETYPE Image); int nVerbose = MIN_VERBOSITY_LEVEL; // std{in,out,err} for redirection of console apps. // See ProcessDebuggeeRedirection() static HANDLE rgh[3] = {0, 0, 0}; DMTLFUNCTYPE DmTlFunc = NULL; BOOL FDMRemote = FALSE; // set true for remote debug BOOL FUseOutputDebugString = FALSE; BOOL FChicago = FALSE; // set true if running on Win95. EXPECTED_EVENT masterEE = {0L,0L}; EXPECTED_EVENT *eeList = &masterEE; static HTHDXSTRUCT masterTH = {0L,0L}; HTHDX thdList = &masterTH; static HPRCXSTRUCT masterPR = {0L,0L}; HPRCX prcList = &masterPR; // control access to thread and process lists: CRITICAL_SECTION csThreadProcList; CRITICAL_SECTION csEventList; // control access to Walk list CRITICAL_SECTION csWalk; HPID hpidRoot = (HPID)INVALID; // this hpid is our hook to the native EM BOOL fUseRoot; // next CREATE_PROCESS will use hpidRoot DEBUG_EVENT64 falseSSEvent; DEBUG_EVENT64 falseBPEvent; DEBUG_EVENT64 FuncExitEvent; METHOD EMNotifyMethod; // Don't allow debug event processing during some shell operations CRITICAL_SECTION csProcessDebugEvent; // Event handles for synchronizing with the shell on proc/thread creates. HANDLE hEventCreateProcess; HANDLE hEventContinue; // event handle for synchronizing connnect/reconnect with the tl HANDLE hEventRemoteQuit; HANDLE hEventNoDebuggee; // set when no debuggee is attached int nWaitingForLdrBreakpoint = 0; BOOL FLoading16 = FALSE; BOOL fDisconnected = FALSE; #ifdef DEBUG_API_INTERLOCK static int ApiLockCount = 0; static PCSTR pszLastFileToLock = NULL; static int nLastFileToLock = 0; static PCSTR pszLastFileToUnlock = NULL; static int nLastFileToUnlock = 0; VOID TakeApiLockFunc( PCSTR pszFile, int nLine ) { if (ApiLockCount && (ULONG_PTR)GetCurrentThreadId() != (ULONG_PTR)csApiInterlock.OwningThread ) { DPRINT(-2, ( (" ** WARN: Waiting on another thread %s %d\n"), pszFile, nLine) ); //assert(!"API lock released when not owned"); } EnterCriticalSection(&csApiInterlock); if (ApiLockCount++ > 1) { DPRINT(-2, ( (" ** ERROR: Locked twice. TakeApiLock %s %d\n"), pszFile, nLine) ); assert(!"API locked twice"); } DPRINT(-1, ( (" ** TakeApiLock %s %d\n"), pszFile, nLine) ); pszLastFileToLock = pszFile; nLastFileToLock = nLine; } BOOL TryApiLockFunc( PCSTR pszFile, int nLine ) { BOOL b = TryEnterCriticalSection(&csApiInterlock); if (b) { ++ApiLockCount; DPRINT(-1, ( ("TryApiLock %s %d\n"), pszFile, nLine) ); pszLastFileToLock = pszFile; nLastFileToLock = nLine; } if (ApiLockCount > 1) { DPRINT(-2, ( (" ** ERROR: Locked twice. TryApiLock %s %d\n"), pszFile, nLine) ); assert(!"API locked twice"); } return b; } VOID ReleaseApiLockFunc( PCSTR pszFile, int nLine ) { DPRINT(-1, ( (" ** ReleaseApiLock %s %d\n"), pszFile, nLine) ) if ((ULONG_PTR)GetCurrentThreadId() != (ULONG_PTR)csApiInterlock.OwningThread ) { DPRINT(-2, ( (" ** ERROR: Not owner. ReleaseApiLock %s %d\n"), pszFile, nLine) ); assert(!"API lock released when not owned"); } if (ApiLockCount < 1) { DPRINT(-2, ( (" ** ERROR: Count is 0. ReleaseApiLock %s %d\n"), pszFile, nLine) ); assert(!"API Lock released when count is 0"); } else { --ApiLockCount; LeaveCriticalSection(&csApiInterlock); } pszLastFileToUnlock = pszFile; nLastFileToUnlock = nLine; } #endif // DEBUG_API_INTERLOCK #ifndef KERNEL // There may be multiple pending debug events. LIST_ENTRY DmListOfPendingDebugEvents; LIST_ENTRY DmFreeListOfDebugEvents; PDETOSAVE GetNextFreeDebugEvent(); // crash dump stuff BOOL CrashDump; PCONTEXT CrashContext; PEXCEPTION_RECORD CrashException; PUSERMODE_CRASHDUMP_HEADER CrashDumpHeader; ULONG64 KiPcrBaseAddress; ULONG64 KiProcessors; HANDLE hDmPollThread = 0; // Handle for event loop thread. BOOL fDmPollQuit = FALSE; // tell poll thread to exit NOW SYSTEM_INFO SystemInfo; OSVERSIONINFO OsVersionInfo; WT_STRUCT WtStruct; // .. for wt VOID Cleanup( VOID ); DWORD WINAPI CallDmPoll( LPVOID lpv ); VOID CrashDumpThread( LPVOID lpv ); void Close3Handles( HANDLE *rgh ); XOSD ProcessDebuggeeRedirection( LPTSTR lszCommandLine, STARTUPINFO FAR * psi ); BOOL SetDebugPrivilege( void ); #endif // !KERNEL #if defined(TARGET_IA64) || defined(TARGET_AXP64) //while we don't have a good crashlib, etc. BOOL DmpGetThread( IN ULONG Processor, OUT PCRASH_THREAD Thread ) { return FALSE; } BOOL DmpDetectVersionParameters( CRASHDUMP_VERSION_INFO* VersionInfo ) { return FALSE; } BOOL DmpInitialize ( IN LPSTR FileName, OUT PCONTEXT *Context, OUT PEXCEPTION_RECORD *Exception, OUT PVOID *DmpHeader ) { return FALSE; } DWORD DmpReadMemory ( IN ULONG64 BaseAddress, IN PVOID Buffer, IN ULONG Size ) { return FALSE; } BOOL DmpGetContext( IN ULONG Processor, OUT PVOID Context ) { return FALSE; } #define xosdDumpInvalidFile xosdUnsupported #define xosdDumpWrongPlatform xosdUnsupported #endif BOOL DmpIsItAUserModeFile(VOID) { return FALSE; } BOOL DmpUserModeTestHeader(VOID) { return FALSE; } #if defined(TARGET_IA64) // AXP64 has disasm VOID CleanupDisassembler(VOID) { }; #endif #ifdef KERNEL extern BOOL fCrashDump; KDOPTIONS KdOptions[] = { _T("BaudRate"), KDO_BAUDRATE, KDT_DWORD, 9600, _T("Port"), KDO_PORT, KDT_DWORD, 2, _T("Cache"), KDO_CACHE, KDT_DWORD, 8192, _T("Verbose"), KDO_VERBOSE, KDT_DWORD, 0, _T("InitialBp"), KDO_INITIALBP, KDT_DWORD, 0, _T("Defer"), KDO_DEFER, KDT_DWORD, 0, _T("UseModem"), KDO_USEMODEM, KDT_DWORD, 0, _T("LogfileAppend"), KDO_LOGFILEAPPEND, KDT_DWORD, 0, _T("GoExit"), KDO_GOEXIT, KDT_DWORD, 0, _T("SymbolPath"), KDO_SYMBOLPATH, KDT_STRING, 0, _T("LogfileName"), KDO_LOGFILENAME, KDT_STRING, 0, _T("CrashDump"), KDO_CRASHDUMP, KDT_STRING, 0 }; VOID GetKernelSymbolAddresses( VOID ); MODULEALIAS ModuleAlias[MAX_MODULEALIAS]; #endif // KERNEL TCHAR nameBuffer[256]; // Reply buffers to and from em char abEMReplyBuf[4096]; // Buffer for EM to reply to us in char abDMReplyBuf[4096]; // Buffer for us to reply to EM requests in LPDM_MSG LpDmMsg = (LPDM_MSG)abDMReplyBuf; // To send a reply of the struct msMyStruct, do this: // LpDmMsg->xosdRet = xosdMyReturnValue // memcpy (LpDmMsg->rgb, &msMyStruct, sizeof (msMyStruct)); // Reply (sizeof (msMyStruct), LpDmMsg, hpid); DDVECTOR DebugDispatchTable[] = { ProcessExceptionEvent, ProcessCreateThreadEvent, ProcessCreateProcessEvent, ProcessExitThreadEvent, ProcessExitProcessEvent, ProcessLoadDLLEvent, ProcessUnloadDLLEvent, ProcessOutputDebugStringEvent, ProcessRipEvent, ProcessBreakpointEvent, NULL, /* CHECK_BREAKPOINT_DEBUG_EVENT */ ProcessSegmentLoadEvent, /* SEGMENT_LOAD_DEBUG_EVENT */ NULL, /* DESTROY_PROCESS_DEBUG_EVENT */ NULL, /* DESTROY_THREAD_DEBUG_EVENT */ NULL, /* ATTACH_DEADLOCK_DEBUG_EVENT */ NULL, /* ATTACH_EXITED_DEBUG_EVENT */ ProcessEntryPointEvent, /* ENTRYPOINT_DEBUG_EVENT */ NULL, /* LOAD_COMPLETE_DEBUG_EVENT */ NULL, /* INPUT_DEBUG_STRING_EVENT */ NULL, /* MESSAGE_DEBUG_EVENT */ NULL, /* MESSAGE_SEND_DEBUG_EVENT */ NULL, /* FUNC_EXIT_EVENT */ #ifdef KERNEL NULL, NULL, #else ProcessOleEvent, /* OLE_DEBUG_EVENT */ ProcessFiberEvent, #endif NULL, /* GENERIC_DEBUG_EVENT */ #ifdef KERNEL NULL, #else ProcessBogusSSEvent, /* BOGUS_WIN95_SINGLESTEP_EVENT */ #endif NULL /* MAX_EVENT_CODE */ }; /* * This array contains the set of default actions to be taken for * all debug events if the thread has the "In Function Evaluation" * bit set. */ DDVECTOR RgfnFuncEventDispatch[] = { EvntException, NULL, /* This can never happen */ NULL, /* This can never happen */ ProcessExitThreadEvent, EvntExitProcess, ProcessLoadDLLEvent, /* Use normal processing */ ProcessUnloadDLLEvent, /* Use normal processing */ ProcessOutputDebugStringEvent, /* Use normal processing */ NULL, EvntBreakpoint, /* Breakpoint processor */ NULL, ProcessSegmentLoadEvent, /* WOW event */ NULL, /* DESTROY_PROCESS_DEBUG_EVENT */ NULL, /* DESTROY_THREAD_DEBUG_EVENT */ NULL, /* ATTACH_DEADLOCK_DEBUG_EVENT */ NULL, /* ATTACH_EXITED_DEBUG_EVENT */ ProcessEntryPointEvent, /* ENTRYPOINT_DEBUG_EVENT */ NULL, /* LOAD_COMPLETE_DEBUG_EVENT */ NULL, /* INPUT_DEBUG_STRING_EVENT */ NULL, /* MESSAGE_DEBUG_EVENT */ NULL, /* MESSAGE_SEND_DEBUG_EVENT */ NULL, /* FUNC_EXIT_EVENT */ NULL, /* OLE_DEBUG_EVENT */ NULL, /* FIBER_DEBUG_EVENT */ NULL, /* GENERIC_DEBUG_EVENT */ #ifdef KERNEL NULL, /* BOGUS_WIN95_SINGLESTEP_EVENT */ #else ProcessBogusSSEvent, /* BOGUS_WIN95_SINGLESTEP_EVENT */ #endif NULL /* MAX_EVENT_CODE */ }; void UNREFERENCED_PARAMETERS(LPVOID lpv,...) { lpv=NULL; } SPAWN_STRUCT SpawnStruct; // packet for causing CreateProcess() DEBUG_ACTIVE_STRUCT DebugActiveStruct; // ... for DebugActiveProcess() #ifndef KERNEL RELOAD_STRUCT ReloadStruct; // for !reload, usermode #endif PKILLSTRUCT KillQueue; CRITICAL_SECTION csKillQueue; BOOL IsExceptionIgnored(HPRCX, DWORD); TCHAR SearchPathString[ 10000 ]; BOOL SearchPathSet; BOOL fUseRealName = FALSE; HINSTANCE hInstance; // The DM DLL's hInstance LPTSTR FmtMsg( int msgid ) { void * lpb; if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | 0, 0, msgid, 0, (LPTSTR)&lpb, 0, 0)) { return (LPTSTR) lpb; } else { return NULL; } } #include "resource.h" TCHAR szErrArg[_MAX_PATH]; LPTSTR LszFromErr( DWORD dwErr ) { #define NTERR(name) ERROR_##name, IDS_##name, static struct { DWORD dwErr; UINT idsErr; } mpiszErr [] = { NTERR(FILE_NOT_FOUND) NTERR(PATH_NOT_FOUND) NTERR(INVALID_HANDLE) NTERR(INVALID_DRIVE) NTERR(INVALID_PARAMETER) NTERR(INVALID_NAME) NTERR(BAD_PATHNAME) NTERR(FILENAME_EXCED_RANGE) NTERR(OUTOFMEMORY) NTERR(NOT_ENOUGH_MEMORY) NTERR(ACCESS_DENIED) NTERR(SHARING_VIOLATION) NTERR(OPEN_FAILED) NTERR(BAD_FORMAT) NTERR(CHILD_NOT_COMPLETE) NTERR(INVALID_MODULETYPE) NTERR(INVALID_EXE_SIGNATURE) NTERR(EXE_MARKED_INVALID) NTERR(BAD_EXE_FORMAT) NTERR(DIRECTORY) 0, IDS_UNKNOWN_ERROR }; static TCHAR rgchErr[256+_MAX_PATH]; // must be static! static TCHAR rgchErrNum[256]; LPTSTR sz = rgchErr; LPTSTR lszFmtErr = NULL; int i; if (!LoadString(hInstance, IDS_COULD_NOT_LOAD_DEBUGGEE, sz, (int)(rgchErr + _tsizeof(rgchErr) - sz))) { assert(FALSE); } sz += _tcslen(sz); // If there's an argument to put in the string, put it here if (szErrArg[0]) { _tcscpy(sz, szErrArg); _tcscat(sz, _T(": ")); sz += _tcslen(sz); } for (i = 0; mpiszErr[i].dwErr; ++i) { if (mpiszErr[ i ].dwErr == dwErr) { break; } } // If we didn't find an error string in our list, call FormatMessage // to get an error message from the operating system. if (mpiszErr[i].dwErr == 0) { lszFmtErr = FmtMsg(dwErr); } // If we got an error message from the operating system, display that, // otherwise display our own message. if (lszFmtErr) { _tcsncpy(sz, lszFmtErr, (int)(rgchErr + _tsizeof(rgchErr) - sz)); rgchErr[_tsizeof(rgchErr)-1] = _T('\0'); } else { // Even if we got through the above loop without finding a match, // we're okay, because the mpiszErr[] table ends with "unknown error". if (!LoadString(hInstance, mpiszErr[i].idsErr, sz, (int)(rgchErr+_tsizeof(rgchErr)-sz))) { assert(FALSE); } } sz += _tcslen(sz); if (!LoadString(hInstance, IDS_NTError, rgchErrNum, _tsizeof(rgchErrNum))) { assert(FALSE); } sz += _stprintf(sz, rgchErrNum, dwErr); return rgchErr; } void SendNTError( HPRCX hprc, DWORD dwErr, LPTSTR lszString ) { LPTSTR lszError; if (lszString) { lszError = lszString; } else { lszError = (LPTSTR) LszFromErr(dwErr); } SendDBCError(hprc, dwErr, lszError); } void SendDBCError( HPRCX hprc, DWORD dwErr, LPTSTR lszString ) { typedef struct _LE { XOSD xosd; TCHAR rgchErr[]; } LE; // load error: passed with dbcError LE FAR * ple; LPRTP lprtp; DWORD cbBuf; LPCTSTR lszError; if (lszString) { lszError = lszString; } else { lszError = ""; // Empty string. } cbBuf = FIELD_OFFSET(RTP, rgbVar) + sizeof(LE) + (_tcslen(lszError) + 1)*sizeof(TCHAR); lprtp = (LPRTP) MHAlloc(cbBuf); assert(lprtp); ple = (LE *) (lprtp->rgbVar); ple->xosd = (XOSD) dwErr; _tcscpy(ple->rgchErr, lszError); lprtp->dbc = dbcError; if (hprc != NULL) { lprtp->hpid= hprc->hpid; } else { lprtp->hpid = hpidRoot; } lprtp->htid = NULL; lprtp->cb = cbBuf; DmTlFunc(tlfDebugPacket, lprtp->hpid, lprtp->cb, (LPARAM) lprtp); MHFree(lprtp); } BOOL ResolveFile( LPTSTR lpName, LPTSTR lpFullName, BOOL fUseRealName ) { DWORD dwAttr; LPTSTR lpFilePart; BOOL fOk; if (fUseRealName) { dwAttr = GetFileAttributes(lpName); fOk = ((dwAttr != 0xffffffff) && ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == 0)); if (fOk) { _ftcscpy(lpFullName, lpName); } } else { fOk = SearchPath(SearchPathString, lpName, NULL, MAX_PATH, lpFullName, &lpFilePart ); if (!fOk) { *lpFullName = 0; } } return fOk; } #ifndef KERNEL XOSD Load( HPRCX hprc, LPCTSTR szAppName, LPCTSTR szArg, LPVOID pattrib, LPVOID tattrib, DWORD creationFlags, BOOL fInheritHandles, CONST LPCTSTR* environment, LPCTSTR currentDirectory, STARTUPINFO FAR * pStartupInfo, LPPROCESS_INFORMATION lppi ) /*++ Routine Description: Arguments: Return Value: TRUE if the process was successfully created and FALSE otherwise. --*/ { XOSD xosd; int type; TCHAR ch, chTerm; int fQuotedFileName; int l; IMAGETYPE Image; LPTSTR lpch; // Just spawning an exec, not debugging BOOL fSpawnOrphan = !(creationFlags & DEBUG_PROCESS || creationFlags & DEBUG_ONLY_THIS_PROCESS); static TCHAR szFullName[MAX_PATH]; static TCHAR szCommandLine[8192]; static TCHAR szCurrentDir[MAX_PATH]; // Directory to spawn the debuggee in. Unreferenced( pattrib ); Unreferenced( tattrib ); Unreferenced( creationFlags ); /* NOTE: Might have to do the same sort of copying for * szArg, pattrib, tattrib and * startupInfo. Determine if this is necessary. */ // global flag to help with special handling of DOS/WOW apps. FLoading16 = FALSE; // Form the command line. // First, we extract the program name and get its full path. Then // we append the arguments. if (szAppName[0] == _T('"')) { // If the user specified a quoted name (ie: a Long File Name, perhaps?), // terminate on the next quote. chTerm = _T('"'); fQuotedFileName=TRUE; szAppName++; // Advance past the quote. } else { // No Quote. Search for the first space. chTerm = _T(' '); fQuotedFileName=FALSE; } // Null terminate the command line if ( (_ftcslen(szAppName) > 2 && szAppName[1] == _T(':')) || szAppName[0] == _T('\\')) { _ftcscpy(szCommandLine, szAppName); fUseRealName = TRUE; } else if (_ftcschr(szAppName, _T('\\')) || !SearchPathSet) { _ftcscpy(szCommandLine, _T(".\\") ); _ftcscat(szCommandLine, szAppName ); fUseRealName = TRUE; } else { if (!*SearchPathString) { _ftcscpy(SearchPathString, _T(".;")); l = 2; l += GetSystemDirectory(SearchPathString+l, _tsizeof(SearchPathString)-l); SearchPathString[l++] = _T(';'); l += GetWindowsDirectory(SearchPathString+l, _tsizeof(SearchPathString)-l); SearchPathString[l++] = _T(';'); GetEnvironmentVariable(_T("PATH"), SearchPathString+l, _tsizeof(SearchPathString)-l); } _ftcscpy(szCommandLine, szAppName); fUseRealName = FALSE; } if (fQuotedFileName) { szAppName--; } // to be similar to the shell, we look for: // .COM // .EXE // nothing // since '.' is a valid filename character on many filesystems, // we don't automatically assume that anything following a '.' // is an "extension." If the extension is "COM" or "EXE", leave // it alone; otherwise try the extensions. lpch = _ftcschr(szCommandLine, _T('.')); if (lpch && ( lpch[1] == 0 || _ftcsicmp(lpch, _T(".COM")) == 0 || _ftcsicmp(lpch, _T(".EXE")) == 0) ) { lpch = NULL; } else { lpch = szCommandLine + _ftcslen(szCommandLine); } *szFullName = 0; if (lpch) { _ftcscpy(lpch, _T(".COM")); ResolveFile(szCommandLine, szFullName, fUseRealName); } if (!*szFullName && lpch) { _ftcscpy(lpch, _T(".EXE")); ResolveFile(szCommandLine, szFullName, fUseRealName); } if (!*szFullName) { if (lpch) { *lpch = 0; } ResolveFile(szCommandLine, szFullName, fUseRealName); } if (!*szFullName) { return xosdFileNotFound; } // Let's do some error checking type = pCharMode(szFullName, &Image); if (System_DmpInvalid == type) { return xosdDumpInvalidFile; } else if (System_DmpWrongPlatform == type) { return xosdDumpWrongPlatform; } else if (System_Invalid == type) { return xosdFileNotFound; } else { switch ( Image ) { case Image_Unknown: // treat as a com file //return xosdBadFormat; case Image_16: FLoading16 = TRUE; #if defined(TARGET_i386) if ( (type == System_GUI) && !(creationFlags & CREATE_SEPARATE_WOW_VDM) && IsWOWPresent() ) { // TODO need dbcError here return xosdGeneral; } break; #else // TODO all platforms will suppport this return xosdGeneral; #endif default: break; } } creationFlags |= (type?CREATE_NEW_CONSOLE:0); // Make sure arg1 (the app's name) is enclosed in quotes if ('"' != *szCommandLine) { // Prepend a double quote PTSTR psz = _ftcsdup(szCommandLine); _ftcscpy(szCommandLine, "\""); _ftcscat(szCommandLine, psz); free(psz); } if ('"' != szCommandLine[_tcslen(szCommandLine)-1]) { // Append a double quote _ftcscat(szCommandLine, "\""); } // Add rest of arguments _ftcscat(szCommandLine, " "); // Ensure a space just incase _ftcscat(szCommandLine, szArg); if (Image == Image_Dump) { // must be a crash dump file if (!StartCrashPollThread()) { return xosdUnknown; } return xosdNone; } if (!fSpawnOrphan) { if (!StartDmPollThread()) { return xosdUnknown; } } // Handle the current directory if ( currentDirectory == NULL ) { TCHAR szDir[_MAX_DIR]; // szCurrentDir is temporarily used to get the drive letter in it. _tsplitpath(szFullName, szCurrentDir, szDir, NULL, NULL); // After the _tcscat szCurrentDir will have the right value. _tcscat(szCurrentDir, szDir); } else { _tcscpy(szCurrentDir, currentDirectory); } ResetEvent(SpawnStruct.hEventApiDone); SpawnStruct.szAppName = szFullName; xosd = ProcessDebuggeeRedirection (szCommandLine, pStartupInfo); if (xosd != xosdNone) { return xosd; } if (pStartupInfo -> dwFlags & STARTF_USESTDHANDLES && !fInheritHandles) { // If we're redirecting the debuggee's STDIO, inheritHandles must be set. assert (FALSE); fInheritHandles = TRUE; } if (!_ftcslen (szCommandLine)) { // In Win 95, if the Args are an empty string, // the AppName is interpreted as with arguments. This fixes it. SpawnStruct.szArgs = NULL; } else { // parse any IO redirection SpawnStruct.szArgs = szCommandLine; } if (!_ftcslen(szCurrentDir)) { SpawnStruct.pszCurrentDirectory = NULL; } else { SpawnStruct.pszCurrentDirectory = szCurrentDir; } SpawnStruct.fdwCreate = creationFlags; SpawnStruct.si = *pStartupInfo; SpawnStruct.fInheritHandles = fInheritHandles; // The second argument to get command line should have both the exe name // and the command line args. GetCommandLine & argv[] are based on the second // argument and we need the exe name to appear there. if (fSpawnOrphan) { // If we're not debugging, it's because we're doing an Execute // (aka SpawnOrphan), so just do it. SpawnStruct.fReturn = CreateProcess( SpawnStruct.szAppName, SpawnStruct.szArgs, NULL, NULL, SpawnStruct.fInheritHandles, SpawnStruct.fdwCreate, NULL, SpawnStruct.pszCurrentDirectory, &SpawnStruct.si, lppi); Close3Handles(rgh); if (!SpawnStruct.fReturn) { SpawnStruct.dwError = GetLastError(); } } else { // This is a semaphore! Set it last! SpawnStruct.fSpawn = TRUE; if (WaitForSingleObject( SpawnStruct.hEventApiDone, INFINITE ) != 0) { SpawnStruct.fReturn = FALSE; SpawnStruct.dwError = GetLastError(); } } if (SpawnStruct.fReturn) { xosd = xosdNone; } else { DPRINT(1, (_T("Failed.\n"))); xosd = xosdGeneral; // make a dbcError with SpawnStruct.dwError SendNTError(hprc, SpawnStruct.dwError, NULL); } return xosd; } #endif // !KERNEL HPRCX InitProcess( HPID hpid ) /*++ Routine Description: Arguments: Return Value: --*/ { HPRCX hprc; /* * Create a process structure, place it * at the head of the master list. */ hprc = (HPRCX)MHAlloc(sizeof(HPRCXSTRUCT)); memset(hprc, 0, sizeof(*hprc)); EnterCriticalSection(&csThreadProcList); hprc->next = prcList->next; prcList->next = hprc; hprc->hpid = hpid; hprc->exceptionList = NULL; hprc->pid = (PID)-1; // Indicates prenatal process hprc->pstate = 0; hprc->cLdrBPWait = 0; hprc->hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); hprc->hEventCreateThread = CreateEvent(NULL, TRUE, TRUE, NULL); hprc->f16bit = FALSE; #ifndef KERNEL hprc->pFbrCntx = NULL; //Ignore Fibers hprc->FbrLst = NULL; hprc->OrpcDebugging = ORPC_NOT_DEBUGGING; hprc->dwKernel32Base = 0; hprc->llnlg = LLInit(sizeof ( NLG ), llfNull, NULL, NLGComp ); #endif InitExceptionList(hprc); LeaveCriticalSection(&csThreadProcList); return hprc; } void ActionDebugNewReady( DEBUG_EVENT64 * pde, HTHDX hthd, DWORDLONG unused, ULONG64 lparam ) /*++ Routine Description: This function is called when a new child process is ready to run. The process is in exactly the same state as in ActionAllDllsLoaded. However, in this case the debugger is not waiting for a reply. Arguments: Return Value: --*/ { HPRCX hprc = (HPRCX)lparam; XOSD xosd = xosdNone; #if defined(INTERNAL) LPBYTE lpbPacket; WORD cbPacket; PDLL_DEFER_LIST pddl; PDLL_DEFER_LIST pddlT; DEBUG_EVENT64 de; #endif DPRINT(5, (_T("Child finished loading\n"))); #ifdef TARGET_i386 hthd->fContextDirty = FALSE; // Undo the change made in ProcessDebugEvent #endif hprc->pstate &= ~ps_preStart; // Clear the loading state flag hprc->pstate |= ps_preEntry; // next stage... hthd->tstate |= ts_stopped; // Set that we have stopped on event --nWaitingForLdrBreakpoint; #if 0 SendDllLoads(hthd); #endif #if defined(INTERNAL) hprc->fNameRequired = TRUE; for (pddl = hprc->pDllDeferList; pddl; pddl = pddlT) { pddlT = pddl->next; de.dwDebugEventCode = LOAD_DLL_DEBUG_EVENT; de.dwProcessId = pde->dwProcessId; de.dwThreadId = pde->dwThreadId; de.u.LoadDll = pddl->LoadDll; if (LoadDll(&de, hthd, &cbPacket, &lpbPacket, FALSE) && (cbPacket != 0)) { NotifyEM(&de, hthd, cbPacket, lpbPacket); } MHFree(pddl); } hprc->pDllDeferList = NULL; #endif /* * Prepare to stop on thread entry point */ SetupEntryBP(hthd); /* * leave it stopped and notify the debugger. */ #if defined(TARGET_ALPHA) || defined(TARGET_AXP64) || defined(TARGET_IA64) SetBPFlag(hthd, EMBEDDED_BP); #endif pde->dwDebugEventCode = LOAD_COMPLETE_DEBUG_EVENT; NotifyEM(pde, hthd, 0, 0L); return; } /* ActionDebugNewReady() */ void ActionDebugActiveReady( DEBUG_EVENT64 * pde, HTHDX hthd, DWORDLONG unused, ULONG64 lparam ) /*++ Routine Description: This function is called when a newly attached process is ready to run. This process is not the same as the previous two. It is either running or at an exception, and a thread has been created by DebugActiveProcess for the sole purpose of hitting a breakpoint. If we have an event handle, it needs to be signalled before the breakpoint is continued. Arguments: Return Value: --*/ { HPRCX hprc = (HPRCX)lparam; XOSD xosd = xosdNone; DPRINT(5, (_T("Active process finished loading\n"))); #ifdef TARGET_i386 hthd->fContextDirty = FALSE; // Undo the change made in ProcessDebugEvent #endif // i386 hprc->pstate &= ~ps_preStart; hthd->tstate |= ts_stopped; // Set that we have stopped on event --nWaitingForLdrBreakpoint; #if 0 // BUGBUG kentf this is not wise. As implemented, this will run code // in the crashed debuggee, calling LoadLibrary and other foolish things. SendDllLoads(hthd); #endif /* * If this is a crashed process, tell the OS * to raise the exception. * Tell the EM that we are finished loading; * it will say GO, and we will catch the exception * soon after. */ if (pde->dwProcessId == DebugActiveStruct.dwProcessId) { if (DebugActiveStruct.hEventGo) { SetEvent(DebugActiveStruct.hEventGo); CloseHandle(DebugActiveStruct.hEventGo); } DebugActiveStruct.dwProcessId = 0; DebugActiveStruct.hEventGo = 0; if (DebugActiveStruct.hProcess != INVALID_HANDLE_VALUE) { CloseHandle(DebugActiveStruct.hProcess); DebugActiveStruct.hProcess = INVALID_HANDLE_VALUE; } SetEvent(DebugActiveStruct.hEventReady); } #if defined(TARGET_ALPHA) || defined(TARGET_AXP64) || defined(TARGET_IA64) SetBPFlag(hthd, EMBEDDED_BP); #endif pde->dwDebugEventCode = LOAD_COMPLETE_DEBUG_EVENT; NotifyEM(pde, hthd, 0, 0L); return; } /* ActionDebugActiveReady() */ void ActionEntryPoint16( DEBUG_EVENT64 * pde, HTHDX hthdx, DWORDLONG unused, DWORDLONG lparam ) /*++ Routine Description: This is the registered event routine called when vdm sends a DBG_TASKSTART notification. Arguments: Return Value: None --*/ { hthdx->hprc->pstate &= ~ps_preEntry; hthdx->tstate |= ts_stopped; NotifyEM(pde, hthdx, 0, ENTRY_BP); } void ActionEntryPoint( DEBUG_EVENT64 * pde, HTHDX hthd, DWORDLONG unused, DWORDLONG lparam ) /*++ Routine Description: This is the registered event routine called when the base exe's entry point is executed. The action we take here depends on whether we are debugging a 32 bit or 16 bit exe. Arguments: pde - Supplies debug event for breakpoint hthd - Supplies descriptor for thread that hit BP unused - unused lparam - unused Return Value: None --*/ { PBREAKPOINT pbp; pbp = AtBP(hthd); assert(pbp); RemoveBP(pbp); // the main reason we are here. ExprBPRestoreDebugRegs(hthd); if (hthd->hprc->f16bit) { // if this is a 16 bit exe, continue and watch for // the task start event. ContinueThread(hthd); } else { // if this is a 32 bit exe, stay stopped and notify the EM hthd->hprc->pstate &= ~ps_preEntry; hthd->tstate |= ts_stopped; pde->dwDebugEventCode = ENTRYPOINT_DEBUG_EVENT; NotifyEM(pde, hthd, 0, ENTRY_BP); DMSqlStartup( hthd->hprc ); } } void HandleDebugActiveDeadlock( HPRCX hprc, BOOL Exited ) { DEBUG_EVENT64 de; HTHDX hthd; // This timed out waiting for the loader // breakpoint. Clear the prestart state, // and tell the EM we are screwed up. // The shell should then stop waiting for // the loader BP. hprc->pstate &= ~ps_preStart; --nWaitingForLdrBreakpoint; ConsumeAllProcessEvents(hprc, TRUE); if (hprc->pid == DebugActiveStruct.dwProcessId) { if (DebugActiveStruct.hEventGo) { SetEvent(DebugActiveStruct.hEventGo); CloseHandle(DebugActiveStruct.hEventGo); } DebugActiveStruct.dwProcessId = 0; DebugActiveStruct.hEventGo = 0; if (DebugActiveStruct.hProcess != INVALID_HANDLE_VALUE) { CloseHandle(DebugActiveStruct.hProcess); DebugActiveStruct.hProcess = INVALID_HANDLE_VALUE; } SetEvent(DebugActiveStruct.hEventReady); } de.dwDebugEventCode = Exited? ATTACH_EXITED_DEBUG_EVENT: ATTACH_DEADLOCK_DEBUG_EVENT; de.dwProcessId = hprc->pid; hthd = hprc->hthdChild; if (hthd) { de.dwThreadId = hthd->tid; } else { de.dwThreadId = 0; } NotifyEM(&de, hthd, 0, 0); } /* HandleDebugActiveDeadlock() */ BOOL SetupSingleStep( HTHDX hthd, BOOL DoContinue ) /*++ Routine Description: description-of-function. Arguments: hthd - Supplies The handle to the thread which is to be single stepped DoContinue - Supplies continuation flag Return Value: TRUE if successfly started step and FALSE otherwise --*/ { PBREAKPOINT pbp; ADDR addr; #if defined(NO_TRACE_FLAG) /* * Set a breakpoint at the next legal offset and mark the breakpoint * as being for a single step. */ AddrInit(&addr, 0, 0, GetNextOffset(hthd, FALSE), TRUE, TRUE, FALSE, FALSE); pbp = SetBP( hthd->hprc, hthd, bptpExec, bpnsStop, &addr, (HPID) INVALID); if ( pbp != NULL ) { pbp->isStep++; } /* * Now issue the command to execute the child */ if ( DoContinue ) { ContinueProcess (hthd->hprc); } #else // NO_TRACE_FLAG #if defined(TARGET_i386) #ifndef KERNEL // Set the single step flag in the context and then start the // thread running // Modify the processor flags in the child's context // If we are passing an exception on to the child we cannot set the // trace flag, so do the non-trace version of this code. if (!PassingExceptionForThisThread (hthd)) { hthd->context.EFlags |= TF_BIT_MASK; hthd->fContextDirty = TRUE; } else { int lpf = 0; hthd->context.EFlags &= ~TF_BIT_MASK; hthd->fContextDirty = TRUE; // this sequence initializes addr to the addr after the current inst AddrFromHthdx (&addr, hthd); IsCall (hthd, &addr, &lpf, FALSE); pbp = SetBP( hthd->hprc, hthd, bptpExec, bpnsStop, &addr, (HPID) INVALID); if ( pbp != NULL ) { pbp->isStep++; } } #endif // KERNEL #elif defined(TARGET_IA64) #ifndef KERNEL // Set the single step flag in the context and then start the // thread running // Modify the processor flags in the child's context // If we are passing an exception on to the child we cannot set the // trace flag, so do the non-trace version of this code. if (!PassingExceptionForThisThread (hthd)) { hthd->context.StIPSR |= TF_BIT_MASK; hthd->fContextDirty = TRUE; } else { int lpf = 0; hthd->context.StIPSR &= ~TF_BIT_MASK; hthd->fContextDirty = TRUE; // this sequence initializes addr to the addr after the current inst AddrFromHthdx (&addr, hthd); IsCall (hthd, &addr, &lpf, FALSE); pbp = SetBP( hthd->hprc, hthd, bptpExec, bpnsStop, &addr, (HPID) INVALID); if ( pbp != NULL ) { pbp->isStep++; } } #endif // KERNEL #else // i386 #error "Need code for new CPU with trace bit" #endif // i386 // Now issue the command to execute the child if ( DoContinue ) { ContinueThreadEx(hthd, (DWORD)DBG_CONTINUE, QT_TRACE_DEBUG_EVENT, ts_running); } #endif // NO_TRACE_FLAG return TRUE; } /* SetupSingleStep() */ VOID ActionReturnStep( DEBUG_EVENT64 * pde, HTHDX hthd, DWORDLONG unused, ULONG64 lparam ) /*++ lpv - stack pointer at the top of the call --*/ { BREAKPOINT* pbp = AtBP(hthd); assert(pbp); if (STACK_POINTER(hthd) > lparam) { RemoveBP(pbp); pde->dwDebugEventCode = EXCEPTION_DEBUG_EVENT; pde->u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP; //ExprBPStop(hthd->hprc, hthd); NotifyEM(pde, hthd, 0, 0); } else { METHOD *ContinueSSMethod; assert(pbp != EMBEDDED_BP); assert(pbp != NULL); ClearBPFlag(hthd); RestoreInstrBP(hthd, pbp); ContinueSSMethod = (METHOD*) MHAlloc(sizeof(METHOD)); ContinueSSMethod->notifyFunction = MethodContinueSS; ContinueSSMethod->lparam = (ULONG64)ContinueSSMethod; ContinueSSMethod->lparam2 = pbp; /* Reenable */ RegisterExpectedEvent(hthd->hprc, hthd, BREAKPOINT_DEBUG_EVENT, (DWORD_PTR) pbp, DONT_NOTIFY, ActionReturnStep, FALSE, lparam ); SingleStepEx(hthd, ContinueSSMethod, FALSE, FALSE, TRUE); } } BOOL SetupReturnStep( HTHDX hthd, BOOL DoContinue, LPADDR lpaddr, LPADDR addrStack ) /*++ Routine Description: description-of-function. Arguments: hthd - Supplies The handle to the thread which is to be single stepped DoContinue - Supplies continuation flag lpaddr - Supplies the address to step to addrStack - Supplies the address for SP. Return Value: TRUE if successfully started step and FALSE otherwise --*/ { BREAKPOINT * pbp; /* */ pbp = SetBP( hthd->hprc, hthd, bptpExec, bpnsStop, lpaddr, (HPID)INVALID); if ( !pbp ) { // On Win95 we might not be able to set the bp because // we might be in a callback and returning to system code. assert(IsChicago()); if (IsInSystemDll(GetAddrOff(*lpaddr))) { SendDBCErrorStep(hthd->hprc); } else { assert(FALSE); // Shouldn't happen any other time. } return FALSE; } if (addrStack) { RegisterExpectedEvent(hthd->hprc, hthd, BREAKPOINT_DEBUG_EVENT, (DWORD_PTR)pbp, DONT_NOTIFY, ActionReturnStep, FALSE, GetAddrOff(*addrStack)); } /* * Now issue the command to execute the child */ if ( DoContinue ) { ExprBPContinue( hthd->hprc, hthd ); ContinueThread (hthd); } return TRUE; } /* SetupReturnStep() */ void SetupEntryBP( HTHDX hthd ) /*++ Routine Description: Set a breakpoint and make a persistent expected event for the entry point of the first thread in a new process. Arguments: hthd - Supplies descriptor for thread to act on. Return Value: None --*/ { ADDR addr; BREAKPOINT * bp; AddrInit(&addr, 0, 0, hthd->lpStartAddress, TRUE, TRUE, FALSE, FALSE); #if defined(TARGET_IA64) { ULONGLONG ullPLabel[2]; DWORD cb; DPRINT(1,(_T("Reading plabel at 0x%I64x-0x%I64x\n"), hthd->lpStartAddress, GetAddrOff(addr))); if (!AddrReadMemory(hthd->hprc, hthd, &addr, &ullPLabel,sizeof(ullPLabel),&cb)) { DPRINT(0,(_T("Could not read the plabel at 0x%I64x -- cb = %ld"),GetAddrOff(addr),cb)); } else { DPRINT(1,("Dereferenced Entry BP from 0x%I64x (plabel?) to 0x%I64x(0x%I64x)\n", GetAddrOff(addr), (UOFFSET) ullPLabel[0], (UOFFSET) ullPLabel[1])); GetAddrOff(addr) = (UOFFSET) ullPLabel[0]; } } #endif bp = SetBP(hthd->hprc, hthd, bptpExec, bpnsStop, &addr, (HPID)ENTRY_BP); // register expected event RegisterExpectedEvent(hthd->hprc, hthd, BREAKPOINT_DEBUG_EVENT, (DWORD_PTR)bp, DONT_NOTIFY, ActionEntryPoint, TRUE, // Persistent! 0 ); } /* SetupEntryBP() */ #ifdef KERNEL VOID RestoreKernelBreakpoints ( HTHDX hthd, ULONG64 Offset ) /*++ Routine Description: Restores all breakpoints in our bp list that fall in the range of offset -> offset+dbgkd_maxstream. This is necessary because the kd stub in the target system clears all breakpoints in this range before delivering an exception to the debugger. Arguments: hthd - handle to the current thread Offset - beginning of the range, usually the current pc Return Value: None --*/ { BREAKPOINT *pbp; DBGKD_WRITE_BREAKPOINT64 bps[MAX_KD_BPS]; DWORD i = 0; #if defined(TARGET_IA64) return; // IA64 version of KdpSetStateChange() will reinstall kernel breakpoints instead. #endif EnterCriticalSection(&csThreadProcList); ZeroMemory( bps, sizeof(bps) ); for (pbp=bpList->next; pbp; pbp=pbp->next) { if (GetAddrOff(pbp->addr) >= Offset && GetAddrOff(pbp->addr) < Offset+DBGKD_MAXSTREAM) { if (i < MAX_KD_BPS) { bps[i++].BreakPointAddress = GetAddrOff(pbp->addr); } } } if (i) { WriteBreakPointEx( hthd, i, bps, 0 ); for (i=0,pbp=bpList->next; pbp; pbp=pbp->next) { if (GetAddrOff(pbp->addr) == bps[i].BreakPointAddress) { pbp->hBreakPoint = bps[i++].BreakPointHandle; } } } LeaveCriticalSection(&csThreadProcList); } #endif // KERNEL #ifndef KERNEL void UpdateThreadContext( HTHDX hthd ) /*++ Routine Description: This routine updates a thread's context, including real/32bit modes and context dirty flags. --*/ { #if defined(TARGET_i386) LDT_ENTRY ldtEntry; #endif hthd->context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT; DbgGetThreadContext( hthd, &hthd->context ); hthd->fContextDirty = FALSE; hthd->fIsCallDone = FALSE; #if !defined( TARGET_i386 ) hthd->fAddrIsReal = FALSE; hthd->fAddrIsFlat = hthd->fAddrOff32 = TRUE; #else // !i386 if (hthd->context.EFlags & V86FLAGS_V86) { hthd->fAddrIsReal = TRUE; hthd->fAddrIsFlat = FALSE; hthd->fAddrOff32 = FALSE; } else { hthd->fAddrIsReal = FALSE; if (PcSegOfHthdx(hthd) == hthd->hprc->segCode) { hthd->fAddrIsFlat = hthd->fAddrOff32 = TRUE; } else { hthd->fAddrIsFlat = FALSE; if (!GetThreadSelectorEntry(hthd->rwHand, PcSegOfHthdx(hthd), &ldtEntry)) { hthd->fAddrOff32 = FALSE; } else if (!ldtEntry.HighWord.Bits.Default_Big) { hthd->fAddrOff32 = FALSE; } else { hthd->fAddrOff32 = TRUE; } } } #endif // !i386 } void ProcessDebugEvent( DEBUG_EVENT64 * de ) /*++ Routine Description: This routine is called whenever a debug event notification comes from the operating system. Arguments: de - Supplies a pointer to the debug event which just occured Return Value: None. --*/ { EXPECTED_EVENT * ee; DWORD eventCode = de->dwDebugEventCode; DWORD_PTR subClass = 0L; HTHDX hthd = NULL; HPRCX hprc; BREAKPOINT * bp; ADDR addr; BP_UNIT instr; DWORD len; BOOL fInstrIsBp; DPRINT(3, (_T("Event Code == %x\n"), eventCode)); hprc = HPRCFromPID(de->dwProcessId); /* * While killing process, ignore everything * except for exit events. */ if (hprc) { hprc->cLdrBPWait = 0; } if ( hprc && (hprc->pstate & ps_killed) ) { if (eventCode == EXCEPTION_DEBUG_EVENT) { AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, (DWORD)DBG_EXCEPTION_NOT_HANDLED, 0); return; } else if (eventCode != EXIT_THREAD_DEBUG_EVENT && eventCode != EXIT_PROCESS_DEBUG_EVENT ) { AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, (DWORD)DBG_EXCEPTION_NOT_HANDLED, 0); return; } } EnterCriticalSection(&csProcessDebugEvent); if (eventCode == CREATE_THREAD_DEBUG_EVENT){ DPRINT(3, (_T("** NEW TID = (PID,TID)(%08lx, %08lx)\n"), de->dwProcessId, de->dwThreadId)); } else { /* * Find our structure for this event's process */ DEBUG_PRINT(_T("Not Create Thread Debug Event\r\n")); hthd = HTHDXFromPIDTID((PID)de->dwProcessId,(TID)de->dwThreadId); // Update the context for all threads, not just the main one. We // may be able to do this a little quicker by using the // fContextDirty flag. // First, update the main threads ExceptionRecord if necessary if (hthd && eventCode == EXCEPTION_DEBUG_EVENT) { hthd->ExceptionRecord = de->u.Exception.ExceptionRecord; } // Loop through all threads and update contexts if (hthd && hprc) { HTHDX hthdT; // If for any reason this is set unset it. #ifndef KERNEL hprc->pFbrCntx = NULL; #endif for (hthdT = hprc->hthdChild; hthdT; hthdT = hthdT->nextSibling) { UpdateThreadContext (hthdT); } } else if (hprc && (hprc->pstate & ps_killed)) { /* * this is an event for a thread that * we never created: */ if (eventCode == EXIT_PROCESS_DEBUG_EVENT) { /* Process exited on a thread we didn't pick up */ ProcessExitProcessEvent(de, NULL); } else { /* this is an exit thread for a thread we never picked up */ AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, DBG_CONTINUE, 0); } goto done; } else if (eventCode!=CREATE_PROCESS_DEBUG_EVENT) { // This will happen sometimes when killing a process with // ProcessTerminateProcessCmd and ProcessUnloadCmd. AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, DBG_CONTINUE, 0); goto done; } } // Mark the thread as having been stopped for some event. if (hthd) { hthd->tstate &= ~ts_running; hthd->tstate |= ts_stopped; } /* If it is an exception event get the subclass */ if (eventCode == EXCEPTION_DEBUG_EVENT) { subClass = de->u.Exception.ExceptionRecord.ExceptionCode; DPRINT(1, (_T("Exception Event: subclass = %p "), subClass)); switch (subClass) { case STATUS_SEGMENT_NOTIFICATION: eventCode = de->dwDebugEventCode = SEGMENT_LOAD_DEBUG_EVENT; break; case EXCEPTION_SINGLE_STEP: #if !defined(TARGET_i386) && !defined(TARGET_IA64) assert(_T("!EXCEPTION_SINGLE_STEP on non-x86!")); #endif #if defined(TARGET_IA64) // v-vadimp - looks like Merced doesn't reset PSR.ss (analog of // EFLAG.tf) after each SS exception, we have to do it manually hthd->fContextDirty = TRUE; hthd->context.StIPSR &= ~TF_BIT_MASK; #endif AddrFromHthdx(&addr, hthd); // This may be a single step or a hardware breakpoint. // If it is a single step, leave it at that. If it is a // hardware breakpoint, convert it to a BREAKPOINT_DEBUG_EVENT. DecodeSingleStepEvent( hthd, de, &eventCode, &subClass ); break; case EXCEPTION_BREAKPOINT: /* * Check if it is a BREAKPOINT exception: * If it is, change the debug event to our pseudo-event, * BREAKPOINT_DEBUG_EVENT (this is a pseudo-event because * the API does not define such an event, and we are * synthesizing not only the class of event but the * subclass as well -- the subclass is set to the appropriate * breakpoint structure) */ AddrFromHthdx(&addr, hthd); #if defined(TARGET_i386) // correct for machine overrun on a breakpoint // On NT the offset value is overrun by 1 every time except // for when doing post-mortem debugging. The value in the // Debug Event structure is always correct though addr.addr.off = de->u.Exception.ExceptionRecord.ExceptionAddress ; #endif // Determine the start of the breakpoint instruction if ((AddrReadMemory(hprc, hthd, &addr, &instr, BP_SIZE, &len) == 0) || (len != BP_SIZE)) { DPRINT(0, ("Memory read failed @ %I64x!!!\n",GetAddrOff(addr))); assert(FALSE); instr = 0; } #if defined(TARGET_ALPHA) || defined(TARGET_AXP64) switch (instr) { case CALLPAL_OP | CALLKD_FUNC: case CALLPAL_OP | BPT_FUNC: case CALLPAL_OP | KBPT_FUNC: fInstrIsBp = TRUE; break; default: fInstrIsBp = FALSE; } DPRINT(3, (_T("Looking for BP@%I64x\n"), addr.addr.off)); #elif defined(TARGET_i386) /* * It may have been a 0xcd 0x03 rather than a 0xcc * (ie: check if it is a 1 or a 2 byte INT 3) */ fInstrIsBp = FALSE; if (instr == BP_OPCODE) { fInstrIsBp = TRUE; } else if (instr == 0x3) { // 0xcd? --addr.addr.off; if (AddrReadMemory(hprc, hthd, &addr, &instr, 1, &len) && (len == 1) && (instr == 0xcd)) { fInstrIsBp = TRUE; } else { ++addr.addr.off; } } Set_PC(hthd, addr.addr.off); hthd->fContextDirty = TRUE; DPRINT(3, (_T("Looking for BP@%I64x (instr=%I64x)\n"), addr.addr.off, (ULONGLONG)instr)); #elif defined(TARGET_IA64) { BP_UNIT ia64_opcode; // EM instruction slots are not byte-aligned. Break instruction // (BP_OPCODE) must be shifted according to the instruction slot // before comparison switch (GetAddrOff(addr) & 0xf) { case 0: ia64_opcode = (instr & (INST_SLOT0_MASK)) >> 5; break; case 4: ia64_opcode = (instr & (INST_SLOT1_MASK)) >> 14; break; case 8: ia64_opcode = (instr & (INST_SLOT2_MASK)) >> 23; break; default: assert(!"Bad BP Address"); ia64_opcode = 0; break; } fInstrIsBp = (ia64_opcode == BP_OPCODE) || (instr == 0); DPRINT(1, (_T("BP test: opcode:%I64x=BP_OPCODE:%x,BP?:%i @ address %I64x\n"), ia64_opcode, BP_OPCODE, fInstrIsBp, addr.addr.off)); } #else #error( "unknown processor type" ) #endif /* * Lookup the breakpoint in our (the dm) table */ bp = FindBP(hthd->hprc, hthd, bptpExec, (BPNS)-1, &addr, FALSE); // We need to be able to find BPs for message BPs as well if (bp == NULL) { bp = FindBP (hthd->hprc, hthd, bptpMessage, (BPNS) -1, &addr, FALSE); } SetBPFlag(hthd, bp?bp:EMBEDDED_BP); if (!bp && !fInstrIsBp) { // If the instruction is not a bp, and there is no record of // the bp, this happened because the exception was already // in the queue when we cleared the bp. // We will just continue it. DPRINT(1, (_T("Continuing false BP.\n"))); ContinueThread(hthd); goto done; } // Q: What does it mean if we find the bp record, but the // instruction is not a bp??? // A: It means that this thread hit the BP after another // thread hit it, but before this thread could be suspended // by the debug subsystem. The debugger cleared the BP // temporarily in order that the other thread could run // past it, and will put it back as soon as that thread // hits the temp BP on the next instruction. //assert(!bp || fInstrIsBp); /* * Reassign the event code to our pseudo-event code */ DPRINT(3, (_T("Reassigning event code!\n"))); /* * For some machines there is not single instruction tracing * on the chip. In this case we need to do it in software. * Check to see if the breakpoint we just hit was there for * doing single step emulation. If so then remap it to * a single step exception. */ if (bp && bp->isStep){ de->u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP; subClass = EXCEPTION_SINGLE_STEP; RemoveBP(bp); break; } /* * Reassign the subclass to point to the correct * breakpoint structure and clear the exception code */ de->dwDebugEventCode = eventCode = BREAKPOINT_DEBUG_EVENT; de->u.Exception.ExceptionRecord.ExceptionAddress = GetAddrOff(addr); de->u.Exception.ExceptionRecord.ExceptionCode = (DWORD)(DWORD_PTR)bp; de->u.Exception.ExceptionRecord.NumberParameters = (DWORD)((DWORD_PTR)bp>>32); subClass = (DWORD_PTR)bp; break; case EXCEPTION_ORPC_DEBUG: { ORPC orpc; // Is this really an OLE notification? if ((orpc = OrpcFromPthd(hthd, de)) != orpcNil) { if (orpc == orpcUnrecognized) { // Unrecognized notification. Resume execution. ContinueThread(hthd); return; } /* ** Reassign the event code to our pseudo-event code */ de->dwDebugEventCode = eventCode = OLE_DEBUG_EVENT; /* ** Reassign the exception code to the type of OLE ** event that occurred. */ de->u.Exception.ExceptionRecord.ExceptionCode = orpc; subClass = orpc; } break; } case EXCEPTION_FIBER_DEBUG: { // If fibers are in use change to fiber event if(hprc->fUseFbrs) { de->dwDebugEventCode = eventCode = FIBER_DEBUG_EVENT; } break; } } } /* * Check if this debug event was expected */ ee = PeeIsEventExpected(hthd, eventCode, subClass, TRUE); /* * If it wasn't, clear all consummable events * and then run the standard handler with * notifications going to the execution model */ assert((0 < eventCode) && (eventCode < MAX_EVENT_CODE)); if (!ee) { if ((hthd != NULL) && (hthd->tstate & ts_funceval)) { assert(RgfnFuncEventDispatch[eventCode-EXCEPTION_DEBUG_EVENT]); RgfnFuncEventDispatch[eventCode-EXCEPTION_DEBUG_EVENT](de, hthd); } else { assert(DebugDispatchTable[eventCode-EXCEPTION_DEBUG_EVENT]); DebugDispatchTable[eventCode-EXCEPTION_DEBUG_EVENT](de,hthd); } } else { /* * If it was expected then call the action * function if one was specified */ if (ee->action) { DPRINT(3,("Calling action @ %p\n",ee->action)); (ee->action)(de, hthd, 0, ee->lparam); } /* * And call the notifier if one was specified */ if (ee->notifier) { METHOD *nm = ee->notifier; DPRINT(3,("Calling notifier @ %p\n",nm)); (nm->notifyFunction)(de, hthd, 0, nm->lparam); } MHFree(ee); } done: LeaveCriticalSection(&csProcessDebugEvent); return; } /* ProcessDebugEvent() */ #else // KERNEL void ProcessDebugEvent( DEBUG_EVENT64 *de, DBGKD_WAIT_STATE_CHANGE64 * sc // Not used anymore ) /*++ Routine Description: This routine is called whenever a debug event notification comes from the operating system. Arguments: de - Supplies a pointer to the debug event which just occured Return Value: None. --*/ { EXPECTED_EVENT * ee; DWORD eventCode = de->dwDebugEventCode; DWORD_PTR subClass = 0; HTHDX hthd = NULL; HPRCX hprc; PBREAKPOINT bp; ADDR addr; DWORD cb; BP_UNIT instr; BOOL fInstrIsBp = FALSE; DPRINT(3, (_T("Event Code == %x\n"), eventCode)); hprc = HPRCFromPID(de->dwProcessId); /* * While killing process, ignore everything * except for exit events. */ if (hprc) { hprc->cLdrBPWait = 0; } if ( hprc && (hprc->pstate & ps_killed) ) { if (eventCode == EXCEPTION_DEBUG_EVENT) { ContinueThreadEx(hthd, (DWORD)DBG_EXCEPTION_NOT_HANDLED, QT_CONTINUE_DEBUG_EVENT, ts_running); return; } else if (eventCode != EXIT_THREAD_DEBUG_EVENT && eventCode != EXIT_PROCESS_DEBUG_EVENT ) { AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, (DWORD)DBG_EXCEPTION_NOT_HANDLED, 0); return; } } EnterCriticalSection(&csProcessDebugEvent); if (eventCode == CREATE_THREAD_DEBUG_EVENT){ DPRINT(3, (_T("** NEW TID = (PID,TID)(%08lx, %08lx)\n"), de->dwProcessId, de->dwThreadId)); } else { /* * Find our structure for this event's process */ hthd = HTHDXFromPIDTID((PID)de->dwProcessId,(TID)de->dwThreadId); /* * Update our context structure for this thread if we found one * in our list. If we did not find a thread and this is * not a create process debug event then return without * processing the event as we are in big trouble. */ if (hthd) { if (eventCode == EXCEPTION_DEBUG_EVENT) { hthd->ExceptionRecord = de->u.Exception.ExceptionRecord; } hthd->context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT; DbgGetThreadContext(hthd,&hthd->context); hthd->fContextDirty = FALSE; hthd->fIsCallDone = FALSE; hthd->fAddrIsReal = FALSE; hthd->fAddrIsFlat = TRUE; hthd->fAddrOff32 = TRUE; } else if (hprc && (hprc->pstate & ps_killed)) { /* * this is an event for a thread that * we never created: */ if (eventCode == EXIT_PROCESS_DEBUG_EVENT) { /* Process exited on a thread we didn't pick up */ ProcessExitProcessEvent(de, NULL); } else { /* this is an exit thread for a thread we never picked up */ AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, DBG_CONTINUE, 0); } goto done; } else if (eventCode!=CREATE_PROCESS_DEBUG_EVENT) { assert(FALSE); AddQueue( QT_CONTINUE_DEBUG_EVENT, de->dwProcessId, de->dwThreadId, DBG_CONTINUE, 0); goto done; } } /* * Mark the thread as having been stopped for some event. */ if (hthd) { hthd->tstate &= ~ts_running; hthd->tstate |= ts_stopped; } /* If it is an exception event get the subclass */ if (eventCode==EXCEPTION_DEBUG_EVENT){ subClass = de->u.Exception.ExceptionRecord.ExceptionCode; DPRINT(1, (_T("Exception Event: subclass = %p "), subClass)); switch (subClass) { case STATUS_SEGMENT_NOTIFICATION: eventCode = de->dwDebugEventCode = SEGMENT_LOAD_DEBUG_EVENT; break; case EXCEPTION_SINGLE_STEP: #if !defined(TARGET_i386) assert(_T("!EXCEPTION_SINGLE_STEP on non-x86!")); #endif AddrFromHthdx(&addr, hthd); RestoreKernelBreakpoints( hthd, GetAddrOff(addr) ); // This may be a single step or a hardware breakpoint. // If it is a single step, leave it at that. If it is // a hardware breakpoint, convert it to a BREAKPOINT_DEBUG_EVENT. DecodeSingleStepEvent( hthd, de, &eventCode, &subClass ); break; case EXCEPTION_BREAKPOINT: /* * Check if it is a BREAKPOINT exception: * If it is, change the debug event to our pseudo-event, * BREAKPOINT_DEBUG_EVENT (this is a pseudo-event because * the API does not define such an event, and we are * synthesizing not only the class of event but the * subclass as well -- the subclass is set to the appropriate * breakpoint structure) */ hthd->fDontStepOff = FALSE; AddrFromHthdx(&addr, hthd); /* * Lookup the breakpoint in our (the dm) table */ bp = FindBP(hthd->hprc, hthd, bptpExec, (BPNS)-1, &addr, FALSE); SetBPFlag(hthd, bp?bp:EMBEDDED_BP); /* * Reassign the event code to our pseudo-event code */ DPRINT(3, (_T("Reassigning event code!\n"))); /* * For some machines there is not single instruction tracing * on the chip. In this case we need to do it in software. * Check to see if the breakpoint we just hit was there for * doing single step emulation. If so then remap it to * a single step exception. */ if (bp) { if (bp->isStep){ de->u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP; subClass = EXCEPTION_SINGLE_STEP; RemoveBP(bp); RestoreKernelBreakpoints( hthd, GetAddrOff(addr) ); break; } else { RestoreKernelBreakpoints( hthd, GetAddrOff(addr) ); } } // Determine the start of the breakpoint instruction // DO NOT use DbgReadMemory here! This is a non-cached read! if (fCrashDump) { cb = DmpReadMemory(GetAddrOff(addr), &instr, BP_SIZE); if (cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } else { if (DmKdReadVirtualMemoryNow(GetAddrOff(addr),&instr,BP_SIZE,&cb) || cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } #if defined(TARGET_ALPHA) || defined(TARGET_AXP64) switch (instr) { case 0: case CALLPAL_OP | CALLKD_FUNC: case CALLPAL_OP | BPT_FUNC: case CALLPAL_OP | KBPT_FUNC: fInstrIsBp = TRUE; break; default: addr.addr.off -= BP_SIZE; // NON-CACHED READ!!! if (fCrashDump) { cb = DmpReadMemory(GetAddrOff(addr),&instr,BP_SIZE); if (cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } else { if (DmKdReadVirtualMemoryNow(GetAddrOff(addr),&instr,BP_SIZE,&cb) || cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } switch (instr) { case 0: case CALLPAL_OP | CALLKD_FUNC: case CALLPAL_OP | BPT_FUNC: case CALLPAL_OP | KBPT_FUNC: fInstrIsBp = TRUE; hthd->fDontStepOff = TRUE; break; default: fInstrIsBp = FALSE; } } #elif defined(TARGET_i386) /* * It may have been a 0xcd 0x03 rather than a 0xcc * (ie: check if it is a 1 or a 2 byte INT 3) */ fInstrIsBp = FALSE; if (instr == BP_OPCODE || instr == 0) { fInstrIsBp = TRUE; } else if (instr == 0x3) { // 0xcd? --addr.addr.off; if (fCrashDump) { cb = DmpReadMemory(GetAddrOff(addr),&instr,BP_SIZE); if (cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } else { if (DmKdReadVirtualMemoryNow(GetAddrOff(addr),&instr,BP_SIZE,&cb) || cb != BP_SIZE) { DPRINT(1, (_T("Memory read failed!!!\n"))); instr = 0; } } if (cb == 1 && instr == 0xcd) { --addr.addr.off; fInstrIsBp = TRUE; } } else { hthd->fDontStepOff = TRUE; } #elif defined(TARGET_IA64) { BP_UNIT ia64_opcode; // EM instruction slots are not byte-aligned. Break instruction // (BP_OPCODE) must be shifted according to the instruction slot // before comparison switch (GetAddrOff(addr) & 0xf) { case 0: ia64_opcode = (instr & (INST_SLOT0_MASK)) >> 5; break; case 4: ia64_opcode = (instr & (INST_SLOT1_MASK)) >> 14; break; case 8: ia64_opcode = (instr & (INST_SLOT2_MASK)) >> 23; break; default: assert(!"Bad BP Address"); ia64_opcode = 0; break; } fInstrIsBp = (ia64_opcode == BP_OPCODE) || (instr == 0); } #else #error( "undefined processor type" ); #endif if (!bp && !fInstrIsBp) { DMPrintShellMsg( _T("Stopped at an unexpected exception: code=%08x addr=%08I64x\n"), de->u.Exception.ExceptionRecord.ExceptionCode, de->u.Exception.ExceptionRecord.ExceptionAddress ); } /* * Reassign the subclass to point to the correct * breakpoint structure and clear the exception code */ de->dwDebugEventCode = eventCode = BREAKPOINT_DEBUG_EVENT; de->u.Exception.ExceptionRecord.ExceptionAddress = addr.addr.off; de->u.Exception.ExceptionRecord.ExceptionCode = (DWORD)(DWORD_PTR)bp; de->u.Exception.ExceptionRecord.NumberParameters = (DWORD)((DWORD_PTR)bp>>32); subClass = (UINT_PTR)bp; break; } } /* * Check if this debug event was expected */ ee = PeeIsEventExpected(hthd, eventCode, subClass, TRUE); /* * If it wasn't, clear all consumable events * and then run the standard handler with * notifications going to the execution model */ assert((0 < eventCode) && (eventCode < MAX_EVENT_CODE)); if (!ee) { if ((hthd != NULL) && (hthd->tstate & ts_funceval)) { assert(RgfnFuncEventDispatch[eventCode-EXCEPTION_DEBUG_EVENT]); RgfnFuncEventDispatch[eventCode-EXCEPTION_DEBUG_EVENT](de, hthd); } else { assert(DebugDispatchTable[eventCode-EXCEPTION_DEBUG_EVENT]); DebugDispatchTable[eventCode-EXCEPTION_DEBUG_EVENT](de,hthd); } } else { /* * If it was expected then call the action * function if one was specified */ if (ee->action) { (ee->action)(de, hthd, 0, ee->lparam); } /* * And call the notifier if one was specified */ if (ee->notifier) { METHOD *nm = ee->notifier; (nm->notifyFunction)(de, hthd, 0, nm->lparam); } MHFree(ee); } done: LeaveCriticalSection(&csProcessDebugEvent); return; } /* ProcessDebugEvent() */ #endif // KERNEL /** ConsumeThreadEventsAndNotifyEM ** Description: ** Does exactly what it says it does */ void ConsumeThreadEventsAndNotifyEM( DEBUG_EVENT64* de, HTHDX hthd, DWORDLONG wParam, DWORDLONG lparam ) { ConsumeAllThreadEvents(hthd, FALSE); NotifyEM(de, hthd, wParam, lparam); } /** NotifyEM ** Synopsis: ** Entry: ** Returns: ** Description: ** Given a debug event from the OS send the correct information ** back to the debugger. */ void NotifyEM( DEBUG_EVENT64* de, HTHDX hthd, DWORDLONG wparam, DWORDLONG lparam ) /*++ Routine Description: This is the interface for telling the EM about debug events. In general, de describes an event which the debugger needs to know about. In some cases, a reply is needed. In those cases this routine handles the reply and does the appropriate thing with the data from the reply. Arguments: de - Supplies debug event structure hthd - Supplies thread that got the event wparam - Supplies data specific to event lparam - Supplies data specific to event Return Value: None --*/ { DWORD eventCode = de->dwDebugEventCode; DWORD subClass; RTP rtp; RTP * lprtp; WORD cbPacket=0; LPBYTE lpbPacket; LPVOID toFree=(LPVOID)0; WORD packetType = tlfDebugPacket; if (hthd) { rtp.hpid = hthd->hprc->hpid; rtp.htid = hthd->htid; } else if (hpidRoot == (HPID)INVALID) { return; } else { // cheat: rtp.hpid = hpidRoot; rtp.htid = NULL; } subClass = de->u.Exception.ExceptionRecord.ExceptionCode; switch(eventCode){ case FUNC_EXIT_EVENT: if (hthd->fDisplayReturnValues) { cbPacket = sizeof(ADDR); rtp.dbc = dbcExitedFunction; lpbPacket = (LPBYTE) lparam; assert(lpbPacket); } else { return; } break; case EXCEPTION_DEBUG_EVENT: if (subClass!=EXCEPTION_SINGLE_STEP){ PEXCEPTION_RECORD64 pexr=&de->u.Exception.ExceptionRecord; DWORD cParam = pexr->NumberParameters; DWORD nBytes = sizeof(EPR)+sizeof(ULONG64)*cParam; LPEPR lpepr = MHAlloc(nBytes); toFree = (LPVOID) lpepr; cbPacket = (WORD) nBytes; lpbPacket = (LPBYTE) lpepr; #ifdef TARGET_i386 lpepr->bpr.segCS = (SEGMENT)hthd->context.SegCs; lpepr->bpr.segSS = (SEGMENT)hthd->context.SegSs; #endif lpepr->bpr.offEBP = FRAME_POINTER(hthd); lpepr->bpr.offESP = STACK_POINTER(hthd); lpepr->bpr.offEIP = PC(hthd); lpepr->bpr.fFlat = hthd->fAddrIsFlat; lpepr->bpr.fOff32 = hthd->fAddrOff32; lpepr->bpr.fReal = hthd->fAddrIsReal; lpepr->efd = (EXCEPTION_FILTER_DEFAULT)lparam; lpepr->dwFirstChance = de->u.Exception.dwFirstChance; lpepr->ExceptionCode = pexr->ExceptionCode; lpepr->ExceptionFlags = pexr->ExceptionFlags; lpepr->NumberParameters = cParam; for(;cParam;cParam--) { lpepr->ExceptionInformation[cParam-1]= pexr->ExceptionInformation[cParam-1]; } rtp.dbc = dbcException; break; }; // Add 'foo returned' to Auto watch window if (hthd->fReturning) { hthd->fReturning = FALSE; NotifyEM(&FuncExitEvent, hthd, 0, (ULONG64)&hthd->addrFrom ); } // Fall through when subClass == EXCEPTION_SINGLE_STEP case BREAKPOINT_DEBUG_EVENT: case ENTRYPOINT_DEBUG_EVENT: case CHECK_BREAKPOINT_DEBUG_EVENT: case LOAD_COMPLETE_DEBUG_EVENT: { LPBPR lpbpr = MHAlloc ( sizeof ( BPR ) ); #ifdef TARGET_i386 UOFFSET bpAddr = PC(hthd)-1; #else UOFFSET bpAddr = PC(hthd); #endif toFree=lpbpr; cbPacket = sizeof ( BPR ); lpbPacket = (LPBYTE) lpbpr; #ifdef TARGET_i386 lpbpr->segCS = (SEGMENT)hthd->context.SegCs; lpbpr->segSS = (SEGMENT)hthd->context.SegSs; #endif lpbpr->offEBP = FRAME_POINTER(hthd); lpbpr->offESP = STACK_POINTER(hthd); lpbpr->offEIP = PC(hthd); lpbpr->fFlat = hthd->fAddrIsFlat; lpbpr->fOff32 = hthd->fAddrOff32; lpbpr->fReal = hthd->fAddrIsReal; lpbpr->qwNotify = lparam; if (eventCode==EXCEPTION_DEBUG_EVENT) { rtp.dbc = dbcStep; break; } else { /* (the breakpoint case) */ // REVIEW: It would be cleaner to setup something which got // called when the EE got consumed and turned this off. I // don't think the DM has this notion except for preset bps. // I think a general function for cleanup would be a good // idea here. if (eventCode != CHECK_BREAKPOINT_DEBUG_EVENT) { hthd->fReturning = FALSE; } if (eventCode == ENTRYPOINT_DEBUG_EVENT) { rtp.dbc = dbcEntryPoint; } else if (eventCode == LOAD_COMPLETE_DEBUG_EVENT) { rtp.dbc = dbcLoadComplete; } else if (eventCode == CHECK_BREAKPOINT_DEBUG_EVENT) { rtp.dbc = dbcCheckBpt; packetType = tlfRequest; } else { rtp.dbc = dbcBpt; } /* NOTE: Ok try to follow this: If this was one * of our breakpoints then we have already * decremented the IP to point to the actual * INT3 instruction (0xCC), this is so we * can just replace the byte and continue * execution from that point on. * But if it was hard coded in by the user * then we can't do anything but execute the * NEXT instruction (because there is no * instruction "under" this INT3 instruction) * So the IP is left pointing at the NEXT * instruction. But we don't want the EM * think that we stopped at the instruction * after the INT3, so we need to decrement * offEIP so it's pointing at the hard-coded * INT3. Got it? * On an ENTRYPOINT_DEBUG_EVENT, the address is * already right, and lparam is ENTRY_BP. */ if (!lparam) { lpbpr->offEIP = (UOFFSET)de-> u.Exception.ExceptionRecord.ExceptionAddress; } } } break; case CREATE_PROCESS_DEBUG_EVENT: /* * A Create Process event has occured. The following * messages need to be sent back * dbceAssignPID: Associate our handle with the debugger * dbcModLoad: Inform the debugger of the module load for * the main exe (this is done at the end of this routine) */ { HPRCX hprc = (HPRCX)lparam; /* * Has the debugger requested this process? * ie: has it already given us the HPID for this process? */ if (hprc->hpid != (HPID)INVALID){ lpbPacket = (LPBYTE)&(hprc->pid); cbPacket = sizeof(hprc->pid); /* Want the hprc for the child NOT the DM */ rtp.hpid = hprc->hpid; #ifndef KERNEL // hack for made-up image names: { extern int MagicModuleId; MagicModuleId = 0; } #endif rtp.dbc = dbceAssignPID; } /* * The debugger doesn't know about this process yet, * request an HPID for this new process. */ else { LPNPP lpnpp = MHAlloc(cbPacket=sizeof(NPP)); toFree = lpnpp; lpbPacket = (LPBYTE)lpnpp; packetType = tlfRequest; /* * We must temporarily assign a valid HPID to this HPRC * because OSDebug will try to de-reference it in the * TL callback function */ rtp.hpid = hpidRoot; lpnpp->pid = hprc->pid; lpnpp->fReallyNew = TRUE; rtp.dbc = dbcNewProc; } } break; case CREATE_THREAD_DEBUG_EVENT: { LPTCR lptcr = (LPTCR) MHAlloc(cbPacket=sizeof(TCR)); toFree = lpbPacket = (LPBYTE)lptcr; packetType = tlfRequest; lptcr->tid = hthd->tid; lptcr->uoffTEB = (UOFFSET) hthd->offTeb; rtp.hpid = hthd->hprc->hpid; rtp.htid = hthd->htid; rtp.dbc = dbcCreateThread; } break; case EXIT_PROCESS_DEBUG_EVENT: cbPacket = sizeof(DWORD); lpbPacket = (LPBYTE) &(de->u.ExitProcess.dwExitCode); hthd->hprc->pstate |= ps_exited; rtp.hpid = hthd->hprc->hpid; rtp.htid = hthd->htid; rtp.dbc = dbcProcTerm; break; case EXIT_THREAD_DEBUG_EVENT: cbPacket = sizeof(DWORD); lpbPacket = (LPBYTE) &(de->u.ExitThread.dwExitCode); hthd->tstate |= ts_dead; /* Mark thread as dead */ hthd->hprc->pstate |= ps_deadThread; rtp.dbc = dbcThreadTerm; break; case DESTROY_PROCESS_DEBUG_EVENT: DPRINT(3, (_T("DESTROY PROCESS\n"))); hthd->hprc->pstate |= ps_destroyed; rtp.dbc = dbcDeleteProc; break; case DESTROY_THREAD_DEBUG_EVENT: /* * Check if already destroyed */ assert( (hthd->tstate & ts_destroyed) == 0 ); DPRINT(3, (_T("DESTROY THREAD\n"))); hthd->tstate |= ts_destroyed; cbPacket = sizeof(DWORD); // kentf exit code is bogus here lpbPacket = (LPBYTE) &(de->u.ExitThread.dwExitCode); rtp.dbc = dbcDeleteThread; break; case LOAD_DLL_DEBUG_EVENT: rtp.dbc = dbcModLoad; lpbPacket = (PVOID)lparam; cbPacket = (USHORT)wparam; //ValidateHeap(); toFree = (LPVOID)lpbPacket; #ifdef KERNEL // if we just loaded the kernel, get magic values // If we are loading the kernel, this need to be synchronous so // that we can load the magic values. // But only for crash dumps. // (we should be able to dispose of this by using KDDEBUGGER_DATA, // but only for NT 5) if (fCrashDump && _tcsicmp( (PTSTR) de->u.LoadDll.lpImageName, KERNEL_IMAGE_NAME ) == 0) { packetType = tlfRequest; } #endif break; case UNLOAD_DLL_DEBUG_EVENT: assert( Is64PtrSE(de->u.UnloadDll.lpBaseOfDll) ); cbPacket = sizeof(ULONG64); lpbPacket = (LPBYTE) &(de->u.UnloadDll.lpBaseOfDll); rtp.dbc = dbceModFree32; break; case OUTPUT_DEBUG_STRING_EVENT: { LPINFOAVAIL lpinf; DWORD cbR; rtp.dbc = dbcInfoAvail; cbPacket = (WORD)(sizeof(INFOAVAIL) + de->u.DebugString.nDebugStringLength + 1); lpinf = (LPINFOAVAIL) lpbPacket = MHAlloc(cbPacket); toFree = lpbPacket; lpinf->fReply = FALSE; lpinf->fUniCode = de->u.DebugString.fUnicode; DbgReadMemory(hthd->hprc, de->u.DebugString.lpDebugStringData, &lpinf->buffer[0], de->u.DebugString.nDebugStringLength, &cbR); lpinf->buffer[cbR] = 0; } break; case INPUT_DEBUG_STRING_EVENT: { // BUGBUG - kcarlos - This code is not UNICODE safe. Should it be? LPINFOAVAIL lpinf; DWORD cbR; packetType = tlfRequest; rtp.dbc = dbcInfoReq; cbPacket = (WORD)(sizeof(INFOAVAIL) + de->u.DebugString.nDebugStringLength + 1); lpinf = (LPINFOAVAIL) lpbPacket = MHAlloc(cbPacket); toFree = lpbPacket; lpinf->fReply = TRUE; lpinf->fUniCode = de->u.DebugString.fUnicode; memcpy(&lpinf->buffer[0], (PVOID)de->u.DebugString.lpDebugStringData, de->u.DebugString.nDebugStringLength ); lpinf->buffer[ de->u.DebugString.nDebugStringLength ] = 0; #if 0 // Protocol changed and someone forgot to update this??? DbgReadMemory(hthd->hprc, de->u.DebugString.lpDebugStringData, &lpinf->buffer[0], de->u.DebugString.nDebugStringLength, &cbR); lpinf->buffer[ cbR ] = 0; #endif } break; case ATTACH_DEADLOCK_DEBUG_EVENT: { static XOSD xosd; xosd = xosdAttachDeadlock; cbPacket = sizeof(xosd); lpbPacket = (LPBYTE) &xosd; rtp.dbc = dbcError; } break; case ATTACH_EXITED_DEBUG_EVENT: { static XOSD xosd; xosd = xosdAttachDeadlock;//** cbPacket = sizeof(xosd); lpbPacket = (LPBYTE) &xosd; rtp.dbc = dbcError; } break; case MESSAGE_DEBUG_EVENT: case MESSAGE_SEND_DEBUG_EVENT: cbPacket = sizeof ( MSGI ); lpbPacket = (LPBYTE) lparam; rtp.dbc = (eventCode == MESSAGE_DEBUG_EVENT) ? dbcMsgBpt :dbcSendMsgBpt; break; default: DPRINT(1, (_T("Error, unknown event\n\r"))); hthd->fExceptionHandled = TRUE; ContinueThread(hthd); return; } DPRINT(3, (_T("Notify the debugger: dbc=%x, hpid=%x, htid=%x, cbpacket=%d "), rtp.dbc, rtp.hpid, rtp.htid, cbPacket+FIELD_OFFSET(RTP, rgbVar))); if (!(rtp.cb=cbPacket)) { DmTlFunc(packetType, rtp.hpid, FIELD_OFFSET(RTP, rgbVar), (LPARAM) &rtp); } else { lprtp = (LPRTP)MHAlloc(FIELD_OFFSET(RTP, rgbVar)+cbPacket); _fmemcpy(lprtp, &rtp, FIELD_OFFSET(RTP, rgbVar)); _fmemcpy(lprtp->rgbVar, lpbPacket, cbPacket); DmTlFunc(packetType, rtp.hpid, (FIELD_OFFSET(RTP, rgbVar)+cbPacket), (LPARAM) lprtp); MHFree(lprtp); } if (toFree) { MHFree(toFree); } DPRINT(3, (_T("\n"))); switch(eventCode){ case CREATE_THREAD_DEBUG_EVENT: if (packetType == tlfRequest) { hthd->htid = *((HTID *) abEMReplyBuf); } SetEvent(hthd->hprc->hEventCreateThread); break; case CREATE_PROCESS_DEBUG_EVENT: if (packetType == tlfRequest) { ((HPRCX)lparam)->hpid = *((HPID *) abEMReplyBuf); } else { XOSD xosd = xosdNone; DmTlFunc( tlfReply, ((HPRCX)lparam)->hpid, sizeof(XOSD), (LPARAM) &xosd); } SetEvent(hEventCreateProcess); break; case OUTPUT_DEBUG_STRING_EVENT: // just here to synchronize break; case INPUT_DEBUG_STRING_EVENT: { de->u.DebugString.nDebugStringLength = _tcslen(abEMReplyBuf) + 1; de->u.DebugString.lpDebugStringData = (DWORD64) MHAlloc(de->u.DebugString.nDebugStringLength); _tcscpy((PVOID) de->u.DebugString.lpDebugStringData, abEMReplyBuf); // Maybe this was intended to be a user mode reply???? // Well it sure doesn't work for kernel! #if 0 DWORD cbR; de->u.DebugString.nDebugStringLength = _ftcslen(abEMReplyBuf) + 1; DbgWriteMemory(hthd->hprc, de->u.DebugString.lpDebugStringData, abEMReplyBuf, de->u.DebugString.nDebugStringLength, &cbR); #endif } break; default: break; } //ValidateHeap(); return; } /* NotifyEM() */ /** ACTIONTERMINATEPROCESS * PURPOSE: * INPUT: * OUTPUT: * EXCEPTIONS: * IMPLEMENTATION: */ VOID ActionTerminateProcess ( DEBUG_EVENT64 *pde, HTHDX hthd, DWORD unused, ULONG64 lparam ) { ConsumeAllProcessEvents ( (HPRCX)lparam, TRUE ); hthd->fExceptionHandled = TRUE; ContinueThread(hthd); hthd->hprc->pstate |= ps_dead; } /* ACTIONTERMINATEPROCESS */ /** ACTIONPROCESSLOADFAILED * PURPOSE: * When we issued the CreateProcess, that succeeded; but then we * got an exception with the EXCEPTION_NONCONTINUABLE flag set, * which means after letting the debuggee continue we would get * a process-termination notification. (For example, if the * system can't find a DLL that the process needs, it sends a * STATUS_DLL_NOT_FOUND notification with this flag set.) So * we've said ok, and now the system has sent us a procterm * notification for it. * INPUT: * OUTPUT: * EXCEPTIONS: * IMPLEMENTATION: */ VOID ActionProcessLoadFailed ( DEBUG_EVENT64 *pde, HTHDX hthd, DWORDLONG unused, ULONG64 lparam ) { /* Call another action proc & do what it does */ ActionTerminateProcess(pde, hthd, 0, (ULONG64)hthd->hprc); } /** ACTIONNONCONTINUABLEEXCEPTION * PURPOSE: * When we issued the CreateProcess, that succeeded; but then we * got an exception with the EXCEPTION_NONCONTINUABLE flag set, * which means after letting the debuggee continue we would get * a process-termination notification. (For example, if the * system can't find a DLL that the process needs, it sends a * STATUS_DLL_NOT_FOUND notification with this flag set.) So * we've said ok, and now the system has sent us a last-chance * notification for it. * INPUT: * OUTPUT: * EXCEPTIONS: * IMPLEMENTATION: */ VOID ActionNoncontinuableException ( DEBUG_EVENT64 *pde, HTHDX hthd, DWORDLONG unused, ULONG64 lparam ) { // [cuda#5673 7/8/93 mikemo] This is very strange -- it seems that // in some situations NT has now sent us a SECOND first-chance // notification, even though it already sent us one. I don't know // why this is, but if this happens, I register another event expecting // a last-chance notification, instead of expecting exit-process. if (pde->u.Exception.dwFirstChance) { RegisterExpectedEvent(hthd->hprc, hthd, EXCEPTION_DEBUG_EVENT, (DWORD_PTR)pde->u.Exception.ExceptionRecord.ExceptionCode, DONT_NOTIFY, ActionNoncontinuableException, FALSE, 0 ); } else { RegisterExpectedEvent(hthd->hprc, hthd, EXIT_PROCESS_DEBUG_EVENT, NO_SUBCLASS, DONT_NOTIFY, ActionProcessLoadFailed, FALSE, 0); } ContinueThreadEx(hthd, (DWORD)DBG_EXCEPTION_NOT_HANDLED, QT_CONTINUE_DEBUG_EVENT, ts_running); } void ProcessExceptionEvent( DEBUG_EVENT64* de, HTHDX hthd ) { DWORD_PTR subclass = de->u.Exception.ExceptionRecord.ExceptionCode; DWORD firstChance = de->u.Exception.dwFirstChance; PBREAKPOINT bp=NULL; EXCEPTION_FILTER_DEFAULT efd; // If the thread is in a pre-start state we failed in the // program loader, probably because a link couldn't be resolved. if ( hthd->hprc->pstate & ps_preStart ) { DPRINT(1, (_T("Exception during init\n"))); // since we will probably never see the expected BP, // clear it out, and clear the prestart flag on the // thread. If the exception is continuable, go ahead // and deliver the exception to the shell as normal. hthd->hprc->pstate &= ~ps_preStart; hthd->tstate |= ts_stopped; ConsumeAllProcessEvents(hthd->hprc, TRUE); #if 0 if (!CrashDump && de->u.Exception.ExceptionRecord.ExceptionFlags & EXCEPTION_NONCONTINUABLE) { // BUGBUG This case does not send an exception record to the shell; instead // BUGBUG it aborts the process and sends an error message. This is probably // BUGBUG undesirable behaviour in a real debugger. DWORD dwRet; LPTSTR lszError = NULL; // The debugger is expecting either an OS-specific error code // or an XOSD. We don't have an OS // error code (all we have is an exception number). The debugger // treats STATUS_DLL_NOT_FOUND as an error code, but doesn't // recognize any other exception codes, so for other exception // codes, just return xosdLoadChild. if (subclass == STATUS_DLL_NOT_FOUND) { TCHAR rgch[256]; // same as magic number in em\errors.c if (!LoadString(hInstance, IDS_STATUS_DLL_NOT_FOUND, rgch, _tsizeof(rgch))) { assert(FALSE); } else { lszError = rgch; } dwRet = xosdFileNotFound; } else { dwRet = xosdGeneral; } SendNTError(hthd->hprc, dwRet, lszError); // Actually the CreateProcess succeeded, and then we // received a notification with an exception; so we need // to keep calling ContinueDebugEvent until the process // is really dead. RegisterExpectedEvent(hthd->hprc, hthd, EXCEPTION_DEBUG_EVENT, subclass, DONT_NOTIFY, ActionNoncontinuableException, FALSE, 0); hthd->hprc->pstate |= ps_exited; hthd->tstate &= ~ts_stopped; AddQueue( QT_CONTINUE_DEBUG_EVENT, hthd->hprc->pid, hthd->tid, (DWORD)DBG_EXCEPTION_NOT_HANDLED, 0); return; } #endif // 0 } switch (subclass) { case EXCEPTION_SINGLE_STEP: #if defined(DOLPHIN) #if defined(NO_TRACE_BIT) // BUGBUG kentf This next bit of code is a rather overcomplicated // BUGBUG way of creating a deadlock. The same task could be // BUGBUG accomplished with considerably less code. // BUGBUG // BUGBUG The problem which this intends to solve is as follows: // BUGBUG When a thread is stepped using a code breakpoint, it // BUGBUG may happen that another thread will execute the // BUGBUG breakpoint. Since that thread is not intended to stop // BUGBUG there, we must replace the instruction and allow that // BUGBUG thread to continue. During the time that the "wrong" // BUGBUG thread is executing the instruction, it may happen that // BUGBUG the "right" thread will hit that instruction, and run // BUGBUG through it, thereby missing the breakpoint. // BUGBUG // BUGBUG The following code attempts to solve the problem by // BUGBUG suspending all of the threads in the process while // BUGBUG stepping the "wrong" thread off of the breakpoint. // BUGBUG // BUGBUG This algorithm assumes that the "wrong" thread will be // BUGBUG able to complete the instruction while all of the other // BUGBUG threads are suspended. This assumption is easily // BUGBUG disproven. For example, try this on a call to // BUGBUG WaitForSingleObject. // BUGBUG // BUGBUG Secondly, this code assumes that the next debug event // BUGBUG will be the exception from the "wrong" thread completing // BUGBUG the new step. This is a absolutely not the case, and // BUGBUG the code below will fail in ways much more disastrous // BUGBUG than running through a step. // BUGBUG // BUGBUG // BUGBUG Note 1: This particular condition cannot arise on // BUGBUG a processor with a step flag, since the step flag // BUGBUG cannot cause a single step exception on the // BUGBUG wrong thread (except as a result of an OS bug [ref:win9x]). { /* This single step was intended for another thread */ HPRCX hprc = hthd->hprc; HTHDX hthdCurr = NULL; BREAKPOINT* tmpBP = NULL; UOFF64 NextOffset; ADDR addr; BP_UNIT opcode = BP_OPCODE; DWORD i; AddrFromHthdx(&addr, hthd); if (bp = AtBP(hthd)) { RestoreInstrBP(hthd, bp); } /* Suspend all threads except this one */ for (hthdCurr = hprc->hthdChild; hthdCurr != NULL; hthdCurr = hthdCurr->nextSibling) { if (hthdCurr != hthd) { if (SuspendThread(hthdCurr->rwHand) == -1L) { ; // Internal error; } } } /* Set BP at next offset so we can get off this SS */ NextOffset = GetNextOffset( hthd, FALSE); if (NextOffset != 0) { ADDR tmpAddr; AddrInit(&tmpAddr, 0, 0, NextOffset, TRUE, TRUE, FALSE, FALSE); tmpBP = SetBP( hprc, hthd, bptpExec, bpnsStop, &tmpAddr, (HPID) INVALID ); assert(tmpBP); } else { assert(FALSE); } /* Get off SS */ VERIFY(ContinueDebugEvent(hprc->pid, hthd->tid, DBG_CONTINUE)); if (DMWaitForDebugEvent(de, INFINITE)) { // this is certain to fire, and for the wrong process assert(de->dwDebugEventCode == EXCEPTION_DEBUG_EVENT); } else { assert(FALSE); } hthd->context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT; VERIFY(DbgGetThreadContext(hthd, &hthd->context)); hthd->fContextDirty = FALSE; hthd->fIsCallDone = FALSE; RemoveBP(tmpBP); /* Resume all threads except this one */ for (hthdCurr = hprc->hthdChild; hthdCurr != NULL; hthdCurr = hthdCurr->nextSibling) { if (hthdCurr != hthd) { if (ResumeThread(hthdCurr->rwHand) == -1L) { assert(FALSE); // Internal error; } } } if (bp == NULL) { bp = SetBP(hprc, hthd, bptpExec, bpnsStop, &addr, FALSE); } else { /* Put back SS */ AddrWriteMemory(hprc, hthd, &bp->addr, (LPBYTE)&opcode, BP_SIZE, &i); bp->instances++; } bp->isStep++; ContinueThread(hthd); } #endif // NO_TRACE_BIT #endif // DOLPHIN break; default: /* * The user can define a set of exceptions for which we do not * notify the shell on a first chance. */ if (!firstChance) { DPRINT(3, (_T("2nd Chance Exception %p.\n"),subclass)); hthd->tstate |= ts_second; efd = efdStop; } else { hthd->tstate |= ts_first; efd = ExceptionAction(hthd->hprc, (ULONG)subclass); switch (efd) { case efdNotify: DPRINT(3, (_T("Notifying EM of Exception %p.\n"),subclass)); NotifyEM(de, hthd, 0, efd); // fall through to ignore case #if defined (TARGET_i386) && !defined (KERNEL) // If we are stepping (=> trace flag set) and we are attempting // to step on a statement that raising an exception that gets // caught somewhere else, then having the trace flag set // prevents us from ending up where the NLG thing takes us. // Turn it off & assume that we will get a second chance. // This is wrong for disasm stepping and may be otherwise // problematic. hthd->context.EFlags &= ~TF_BIT_MASK; hthd->fContextDirty = TRUE; #endif #if defined (TARGET_IA64) && !defined (KERNEL) // If we are stepping (=> trace flag set) and we are attempting // to step on a statement that raising an exception that gets // caught somewhere else, then having the trace flag set // prevents us from ending up where the NLG thing takes us. // Turn it off & assume that we will get a second chance. // This is wrong for disasm stepping and may be otherwise // problematic. hthd->context.StIPSR &= ~TF_BIT_MASK; hthd->fContextDirty = TRUE; #endif case efdIgnore: DPRINT(3, (_T("Ignoring Exception %p.\n"),subclass)); ContinueThreadEx(hthd, DBG_EXCEPTION_NOT_HANDLED, QT_CONTINUE_DEBUG_EVENT, ts_running); return; case efdStop: case efdCommand: break; } } // Cleanup ... hthd->fReturning = FALSE; ConsumeAllThreadEvents(hthd, FALSE); //ExprBPStop(hthd->hprc, hthd); NotifyEM(de, hthd, 0, efd); break; } return; } /* ProcessExceptionEvent() */ void ProcessRipEvent( DEBUG_EVENT64 * de, HTHDX hthd ) { if (hthd) { hthd->tstate |= ts_rip; } NotifyEM( de, hthd, 0, 0 ); } /* ProcessRipEvent() */ void ProcessBreakpointEvent( DEBUG_EVENT64 * pde, HTHDX hthd ) { PBREAKPOINT pbp = (PBREAKPOINT) ((((DWORD_PTR)pde->u.Exception.ExceptionRecord.NumberParameters) << 32) | (DWORD)pde->u.Exception.ExceptionRecord.ExceptionCode); #if 0 // This code doesn't do pointer extension properly PBREAKPOINT pbp; DWORD_PTR ibp = pde->u.Exception.ExceptionRecord.ExceptionCode; ibp |= pde->u.Exception.ExceptionRecord.NumberParameters << 32; pbp = (PBREAKPOINT)ibp; #endif pde->u.Exception.ExceptionRecord.ExceptionCode = 0; pde->u.Exception.ExceptionRecord.NumberParameters = 0; DPRINT(1, (_T("Hit a breakpoint -- "))); if (!pbp) { DPRINT(1, (_T("[Embedded BP]\n"))); // already set this in ProcessDebugEvent //SetBPFlag(hthd, EMBEDDED_BP); NotifyEM(pde, hthd, 0, (ULONG64)pbp); } else if (!pbp->hthd || pbp->hthd == hthd) { DPRINT(1, (_T("[One of our own BP's.]\n"))); if (pbp->bpType == bptpMessage) { #ifdef KERNEL assert(!"Message BP not allowed in Kernel mode"); NotifyEM(pde, hthd, 0, (ULONG64)pbp); #else MSGI msgi; if (!GetWndProcMessage (hthd, &msgi.dwMessage)) { assert (FALSE); } msgi.dwMask = 0; // mask is currently unused in the dm msgi.addr = pbp->addr; pde->dwDebugEventCode = MESSAGE_DEBUG_EVENT; SetBPFlag (hthd, pbp); ConsumeAllThreadEvents (hthd, FALSE); NotifyEM (pde, hthd, 0, (ULONG64)&msgi); #endif // KERNEL } else if (CheckDataBP (hthd, pbp)) { SetBPFlag(hthd, pbp); if ((pbp->bpNotify == bpnsStop) || (pbp->bpNotify == bpnsCheck && CheckBpt(hthd, pbp))) { ConsumeAllThreadEvents(hthd, FALSE); NotifyEM(pde, hthd, 0, (ULONG64)pbp); } else { if (pbp->bpNotify == bpnsContinue) { // NotifyEM(...); } ContinueFromBP(hthd, pbp); } } else { ContinueFromBP(hthd, pbp); } } else { DPRINT(1, (_T("[BP for another thread]\n"))); // data bp should never hit on the wrong thread assert(pbp->hWalk == NULL); ContinueFromBP(hthd, pbp); } } VOID ContinueFromBP( HTHDX hthd, PBREAKPOINT pbp ) { METHOD *ContinueSSMethod; if (!AtBP(hthd)) { ExprBPContinue( hthd->hprc, hthd ); ContinueThread(hthd); } else { ContinueSSMethod = (METHOD*)MHAlloc(sizeof(METHOD)); ContinueSSMethod->notifyFunction = MethodContinueSS; ContinueSSMethod->lparam = (ULONG64)ContinueSSMethod; ContinueSSMethod->lparam2 = pbp; RestoreInstrBP(hthd, pbp); SingleStep(hthd, ContinueSSMethod, FALSE, FALSE); } } VOID StuffHthdx( DEBUG_EVENT64 *de, HPRCX hprc, HTHDX hthd ) /*++ Routine Description: Common code for CreateProcess and CreateThread events. Stuff the hthd with default values, add the new structure to the process' chain. Critical section csThreadProcList must be held when calling this routine. Arguments: de - Supplies debug event hprc - Supplies process structure hthd - Supplies empty thread struct, returns full one. Return Value: --*/ { hthd->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse = thdList->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse; thdList->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse = hthd; hthd->nextSibling = hprc->hthdChild; hprc->hthdChild = (LPVOID)hthd; hthd->hprc = hprc; hthd->htid = 0; hthd->atBP = (BREAKPOINT*)0; hthd->tstate = ts_stopped; hthd->fAddrIsReal = FALSE; hthd->fAddrIsFlat = TRUE; hthd->fAddrOff32 = TRUE; hthd->fWowEvent = FALSE; hthd->fStopOnNLG = FALSE; hthd->fReturning = FALSE; hthd->fDisplayReturnValues = TRUE; #ifdef KERNEL hthd->fContextStale = TRUE; #endif #ifndef KERNEL hthd->pcs = NULL; hthd->poleret = NULL; #endif InitializeListHead(&hthd->WalkList); hthd->tid = de->dwThreadId; if (de->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { hthd->rwHand = de->u.CreateProcessInfo.hThread; hthd->offTeb = (OFFSET) de->u.CreateProcessInfo.lpThreadLocalBase; hthd->lpStartAddress= de->u.CreateProcessInfo.lpStartAddress; } else { hthd->rwHand = de->u.CreateThread.hThread; hthd->offTeb = (OFFSET) de->u.CreateThread.lpThreadLocalBase; hthd->lpStartAddress= de->u.CreateThread.lpStartAddress; } } void ProcessCreateProcessEvent( DEBUG_EVENT64 * pde, HTHDX hthd ) /*++ Routine Description: This routine does the processing needed for a create process event from the OS. We need to do the following: - Set up our internal structures for the newly created thread and process - Get notifications back to the debugger Arguments: pde - Supplies pointer to the DEBUG_EVENT structure from the OS hthd - Supplies thread descriptor that thread event occurred on Return Value: none --*/ { DEBUG_EVENT64 de2; CREATE_PROCESS_DEBUG_INFO64 *pcpd = &pde->u.CreateProcessInfo; HPRCX hprc; LPBYTE lpbPacket; WORD cbPacket; DEBUG_PRINT(_T("ProcessCreateProcessEvent\r\n")); ResetEvent(hEventCreateProcess); hprc = InitProcess((HPID)INVALID); // Stuff the process structure if (fUseRoot) { hprc->pstate |= ps_root; hprc->hpid = hpidRoot; fUseRoot = FALSE; if (pcpd->lpStartAddress && FLoading16) { hprc->f16bit = TRUE; } else { hprc->f16bit = FALSE; } } hprc->pid = pde->dwProcessId; hprc->pstate |= ps_preStart; ResetEvent(hprc->hEventCreateThread); // Create the first thread structure for this app hthd = (HTHDX)MHAlloc(sizeof(HTHDXSTRUCT)); memset(hthd, 0, sizeof(*hthd)); EnterCriticalSection(&csThreadProcList); StuffHthdx(pde, hprc, hthd); LeaveCriticalSection(&csThreadProcList); #ifndef KERNEL #if defined(TARGET_i386) hthd->context.ContextFlags = CONTEXT_FULL; DbgGetThreadContext( hthd, &hthd->context ); hprc->segCode = (SEGMENT) hthd->context.SegCs; #endif // i386 #endif // !KERNEL #ifndef KERNEL // try to create a handle with more permissions if (!CrashDump) { hprc->rwHand = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pde->dwProcessId); if (hprc->rwHand) { hprc->CloseProcessHandle = TRUE; } else { hprc->rwHand = pcpd->hProcess; } } #endif // KERNEL /* * There is going to be a breakpoint to announce that the * process is loaded and runnable. */ nWaitingForLdrBreakpoint++; if (pcpd->lpStartAddress == 0) { // in an attach, the BP will be in another thread. RegisterExpectedEvent( hprc, (HTHDX)NULL, BREAKPOINT_DEBUG_EVENT, NO_SUBCLASS, DONT_NOTIFY, ActionDebugActiveReady, FALSE, (ULONG64)hprc); #ifndef KERNEL #if defined(INTERNAL) // don't ever let it defer name resolutions hprc->fNameRequired = TRUE; #endif #endif } else { // On a real start, the BP will be in the first thread. RegisterExpectedEvent( hthd->hprc, hthd, BREAKPOINT_DEBUG_EVENT, NO_SUBCLASS, DONT_NOTIFY, ActionDebugNewReady, FALSE, (ULONG64)hprc); } // Notify the EM of this newly created process. // If not the root proc, an hpid will be created and added // to the hprc by the em. // We enter a crtitcal section to block the UI thread from // querying the DM's prcList before it has been fully // initialized. EnterCriticalSection(&csThreadProcList); NotifyEM(pde, hthd, 0, (ULONG64)hprc); LeaveCriticalSection(&csThreadProcList); #ifndef KERNEL /* * We also need to drop out a module load notification * on this exe. */ assert( Is64PtrSE(pde->u.CreateProcessInfo.lpBaseOfImage) ); de2.dwDebugEventCode = LOAD_DLL_DEBUG_EVENT; de2.dwProcessId = pde->dwProcessId; de2.dwThreadId = pde->dwThreadId; de2.u.LoadDll.hFile = pde->u.CreateProcessInfo.hFile; de2.u.LoadDll.lpBaseOfDll = pde->u.CreateProcessInfo.lpBaseOfImage; de2.u.LoadDll.lpImageName = pde->u.CreateProcessInfo.lpImageName; de2.u.LoadDll.fUnicode = pde->u.CreateProcessInfo.fUnicode; if (LoadDll(&de2, hthd, &cbPacket, &lpbPacket, FALSE) && (cbPacket != 0)) { NotifyEM(&de2, hthd, cbPacket, (ULONG64)lpbPacket); } #endif // !KERNEL /* * Fake up a thread creation notification. */ pde->dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT; NotifyEM(pde, hthd, 0, (ULONG64)hprc); /* * Dont let the new process run: the shell will say Go() * after receiving a CreateThread event. */ } /* ProcessCreateProcessEvent() */ void ProcessCreateThreadEvent( DEBUG_EVENT64 *pde, HTHDX creatorHthd ) { CREATE_THREAD_DEBUG_INFO64 *ctd = &pde->u.CreateThread; HTHDX hthd; HPRCX hprc; CONTEXT context; #if defined(KERNEL) && defined(HAS_DEBUG_REGS) KSPECIAL_REGISTERS ksr; #endif // KERNEL && i386 Unreferenced(creatorHthd); DPRINT(3, (_T("\n**CREATE THREAD EVENT\n"))); // Determine the hprc hprc= HPRCFromPID(pde->dwProcessId); ResetEvent(hprc->hEventCreateThread); if (ctd->hThread == NULL) { DPRINT(1, (_T("BAD HANDLE! BAD HANDLE!(%08lx)\n"), ctd->hThread)); AddQueue( QT_CONTINUE_DEBUG_EVENT, pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE, 0); return; } if (!hprc) { DPRINT(1, (_T("BAD PID! BAD PID!\n"))); AddQueue( QT_CONTINUE_DEBUG_EVENT, pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE, 0); return; } // Create the thread structure hthd = (HTHDX)MHAlloc(sizeof(HTHDXSTRUCT)); memset( hthd, 0, sizeof(*hthd)); // Stuff the structure EnterCriticalSection(&csThreadProcList); StuffHthdx(pde, hprc, hthd); LeaveCriticalSection(&csThreadProcList); // Let the expression breakpoint manager that a new thread // has been created. ExprBPCreateThread( hprc, hthd ); // initialize cache entries context.ContextFlags = CONTEXT_FULL; DbgGetThreadContext( hthd, &context ); #if defined(KERNEL) && defined(HAS_DEBUG_REGS) GetExtendedContext( hthd, &ksr ); #endif // KERNEL && i386 // Notify the EM of this new thread if (fDisconnected) { ContinueThread(hthd); } else { NotifyEM(pde, hthd, 0, (ULONG64)hprc); } return; } void ProcessExitProcessEvent( DEBUG_EVENT64* pde, HTHDX hthd ) { HPRCX hprc; XOSD xosd; HTHDX hthdT; BREAKPOINT * pbp; BREAKPOINT * pbpT; DPRINT(3, (_T("ProcessExitProcessEvent\n"))); /* * do all exit thread handling: * If thread was created during/after the * beginning of termination processing, we didn't * pick it up, so don't try to destroy it. */ if (!hthd) { hprc = HPRCFromPID(pde->dwProcessId); } else { hprc = hthd->hprc; hthd->tstate |= ts_dead; hthd->dwExitCode = pde->u.ExitProcess.dwExitCode; } #ifndef KERNEL DEBUG_PRINT(_T("Try unloading modules.\r\n")); UnloadAllModules( hprc, hthd? hthd:hprc->hthdChild, FALSE, TRUE ); DEBUG_PRINT(_T("Done with UnloadAllModules.\r\n")); #endif /* and exit process */ hprc->pstate |= ps_dead; hprc->dwExitCode = pde->u.ExitProcess.dwExitCode; ConsumeAllProcessEvents(hprc, TRUE); /* * Clean up BP records */ for (pbp = BPNextHprcPbp(hprc, NULL); pbp; pbp = pbpT) { pbpT = BPNextHprcPbp(hprc, pbp); RemoveBP(pbp); } #ifndef KERNEL /* * Clean up any Fibers that are left. */ RemoveFiberList(hprc); #endif /* * If we haven't seen EXIT_THREAD events for any * threads, we aren't going to, so consider them done. */ for (hthdT = hprc->hthdChild; hthdT; hthdT = hthdT->nextSibling) { if ( !(hthdT->tstate & ts_dead) ) { hthdT->tstate |= ts_dead; hthdT->tstate &= ~ts_stopped; } } /* * If process hasn't initialized yet, we were expecting * a breakpoint to notify us that all the DLLs are here. * We didn't get that yet, so reply here. */ if (hprc->pstate & ps_preStart) { xosd = xosdUnknown; DmTlFunc( tlfReply, hprc->hpid, sizeof(XOSD), (LPARAM) &xosd); } if (!(hprc->pstate & ps_killed)) { assert(hthd); pde->dwDebugEventCode = EXIT_THREAD_DEBUG_EVENT; pde->u.ExitThread.dwExitCode = hprc->dwExitCode; NotifyEM(pde, hthd, 0, 0); } else { /* * If ProcessTerminateProcessCmd() killed this, * silently continue the event and release the semaphore. * Don't notify the EM of anything; ProcessUnloadCmd() * will do that for any undestroyed threads. */ AddQueue( QT_CONTINUE_DEBUG_EVENT, pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE, 0); } DMSqlTerminate(hprc); SetEvent(hprc->hExitEvent); } /* ProcessExitProcessEvent() */ void ProcessExitThreadEvent( DEBUG_EVENT64* pde, HTHDX hthd ) { HPRCX hprc = hthd->hprc; DPRINT(3, (_T("** ProcessExitThreadEvent, hthd == %p\n"), hthd)); hthd->tstate |= ts_dead; if (hthd->tstate & ts_frozen) { ResumeThread(hthd->rwHand); hthd->tstate &= ~ts_frozen; } hthd->dwExitCode = pde->u.ExitThread.dwExitCode; /* * Free all events for this thread */ ConsumeAllThreadEvents(hthd, TRUE); // Let the Expression Breakpoint manager know that this thread // is gone. ExprBPExitThread( hprc, hthd ); if (hprc->pstate & ps_killed) { AddQueue( QT_CONTINUE_DEBUG_EVENT, hthd->hprc->pid, hthd->tid, DBG_CONTINUE, 0); } else if (fDisconnected) { hthd->hprc->pstate |= ps_exited; AddQueue( QT_CONTINUE_DEBUG_EVENT, hthd->hprc->pid, hthd->tid, DBG_CONTINUE, 0); } else { NotifyEM(pde, hthd, 0, 0); } return; } /* ProcessExitThreadEvent() */ void ProcessLoadDLLEvent( DEBUG_EVENT64* de, HTHDX hthd ) { LPBYTE lpbPacket; WORD cbPacket; if (LoadDll(de, hthd, &cbPacket, &lpbPacket, TRUE) || (cbPacket == 0)) { NotifyEM(de, hthd, cbPacket, (ULONG64)lpbPacket); } return; } /* ProcessLoadDLLEvent() */ void ProcessUnloadDLLEvent( DEBUG_EVENT64* pde, HTHDX hthd ) { int iDll; HPRCX hprc = hthd->hprc; DPRINT(10, (_T("** UnloadDll %I64x\n"), pde->u.UnloadDll.lpBaseOfDll)); assert( Is64PtrSE(pde->u.UnloadDll.lpBaseOfDll) ); for (iDll = 0; iDll < hprc->cDllList; iDll++) { if (hprc->rgDllList[iDll].fValidDll) { assert( Is64PtrSE(hprc->rgDllList[iDll].offBaseOfImage) ); if (hprc->rgDllList[iDll].offBaseOfImage == (OFFSET) pde->u.UnloadDll.lpBaseOfDll) { break; } } } /* * Make sure that we found a legal address. If not then assert * and check for problems. */ #ifndef KERNEL // this happens all the time in kernel mode // when user mode modloads are enabled assert( iDll != hprc->cDllList ); #endif if (iDll != hprc->cDllList) { if (!fDisconnected) { NotifyEM(pde, hthd, 0, 0); } DestroyDllLoadItem( &hprc->rgDllList[iDll] ); } ContinueThread(hthd); return; } void DestroyDllLoadItem( PDLLLOAD_ITEM pDll ) { if (pDll->szDllName) { MHFree(pDll->szDllName); pDll->szDllName = NULL; } #ifdef KERNEL if (pDll->sec) { MHFree(pDll->sec); pDll->sec = NULL; } #endif pDll->offBaseOfImage = 0; pDll->cbImage = 0; pDll->fValidDll = FALSE; return; } void ProcessOutputDebugStringEvent(DEBUG_EVENT64* de, HTHDX hthd) /*++ Routine Description: Handle an OutputDebugString from the debuggee Arguments: de - Supplies DEBUG_EVENT64 struct hthd - Supplies thread descriptor for thread that generated the event Return Value: None --*/ { int cb = de->u.DebugString.nDebugStringLength; #if DBG DWORD cbR; char rgch[256]; HANDLE rwHand; cb = min(cb, 256); rwHand = hthd->hprc->rwHand; DbgReadMemory(hthd->hprc, de->u.DebugString.lpDebugStringData, rgch, cb, &cbR); rgch[cbR] = 0; DPRINT(3, (_T("%s\n"), rgch)); #endif NotifyEM(de, hthd, 0, 0); ContinueThread(hthd); return; } #ifndef KERNEL void ProcessBogusSSEvent( DEBUG_EVENT64* de, HTHDX hthd ) /*++ Routine Description: Handle a Bogus Win95 Single step event. Just continue execution as if nothing happened. Arguments: de - Supplies DEBUG_EVENT64 struct hthd - Supplies thread descriptor for thread that generated the event Return Value: None --*/ { ContinueThread(hthd); return; } #endif void Reply( UINT length, LPVOID lpbBuffer, HPID hpid ) /*++ Routine Description: Send a reply packet to a tlfRequest from the EM Arguments: length - Supplies length of reply lpbBuffer - Supplies reply data hpid - Supplies handle to EM process descriptor Return Value: None --*/ { /* * Add the size of the packet header to the length */ length += FIELD_OFFSET(DM_MSG, rgb); DPRINT(5, (_T("Reply to EM [%d]\n"), length)); assert(length <= sizeof(abDMReplyBuf) || lpbBuffer != abDMReplyBuf); if (DmTlFunc) { // IF there is a TL loaded, reply DmTlFunc(tlfReply, hpid, length, (LPARAM) lpbBuffer); } return; } #ifdef KERNEL #define DMAPILOCK() \ if (!TryApiLock()) {\ LpDmMsg->xosdRet = xosdTargetIsRunning;\ Reply( sizeof( XOSD ), LpDmMsg, lpdbb->hpid );\ break;\ } #define DMAPIRELEASE() ReleaseApiLock() #else #define DMAPILOCK() #define DMAPIRELEASE() #endif VOID FAR PASCAL DMFunc( DWORD cb, LPDBB lpdbb ) /*++ Routine Description: This is the main entry point for the DM. This takes dmf message packets from the debugger and handles them, usually by dispatching to a worker function. Arguments: cb - supplies size of data packet lpdbb - supplies pointer to packet Return Value: --*/ { DMF dmf; HPRCX hprc; HTHDX hthd; XOSD xosd = xosdNone; dmf = (DMF) (lpdbb->dmf & 0xffff); DEBUG_PRINT(_T("DMFunc\r\n")); DPRINT(5, (_T("DmFunc [%2x] "), dmf)); hprc = HPRCFromHPID(lpdbb->hpid); hthd = HTHDXFromHPIDHTID(lpdbb->hpid, lpdbb->htid); switch ( dmf ) { case dmfSelLim: DPRINT(5, (_T("dmfSelLim\n"))); DMAPILOCK(); ProcessSelLimCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfSetMulti: DPRINT(5, (_T("dmfSetMulti\n"))); LpDmMsg->xosdRet = xosdNone; Reply( 0, LpDmMsg, lpdbb->hpid ); break; case dmfClearMulti: DPRINT(5, (_T("dmfClearMulti\n"))); LpDmMsg->xosdRet = xosdNone; Reply( 0, LpDmMsg, lpdbb->hpid ); break; case dmfDebugger: DPRINT(5, (_T("dmfDebugger\n"))); LpDmMsg->xosdRet = xosdNone; Reply( 0, LpDmMsg, lpdbb->hpid ); break; case dmfCreatePid: DPRINT(5,(_T("dmfCreatePid\r\n"))); ProcessCreateProcessCmd(hprc, hthd, lpdbb); break; case dmfDestroyPid: DPRINT(5, (_T("dmfDestroyPid\n"))); LpDmMsg->xosdRet = FreeProcess(hprc, TRUE); Reply( 0, LpDmMsg, lpdbb->hpid); break; case dmfSpawnOrphan: DPRINT(5, (_T("dmfSpawnOrphan\n"))); #ifdef KERNEL assert(!"Spawn orphan not allowed in kernel mode"); LpDmMsg->xosdRet = xosdUnknown; Reply( 0, LpDmMsg, lpdbb->hpid); #else ProcessSpawnOrphanCmd (hprc, hthd, lpdbb); #endif break; case dmfProgLoad: DPRINT(5, (_T("dmfProgLoad\n"))); ProcessProgLoadCmd(hprc, hthd, lpdbb); break; case dmfProgFree: DPRINT(5, (_T("dmfProgFree\n"))); #ifndef KERNEL ProcessTerminateProcessCmd(hprc, hthd, lpdbb); #else // KERNEL if (!hprc) { LpDmMsg->xosdRet = xosdNone; Reply( 0, LpDmMsg, lpdbb->hpid); break; } if (KdOptions[KDO_GOEXIT].value) { HTHDX hthdT; PBREAKPOINT bp; KdOptions[KDO_GOEXIT].value = 0; for (hthdT = hprc->hthdChild; hthdT; hthdT = hthdT->nextSibling) { if (hthdT->tstate & ts_stopped) { if (bp = AtBP(hthdT)) { if (!hthdT->fDontStepOff) { IncrementIP(hthdT); } } if (hthdT->fContextDirty) { DbgSetThreadContext( hthdT, &hthdT->context ); hthdT->fContextDirty = FALSE; } KdOptions[KDO_GOEXIT].value = 1; break; } } } DMAPILOCK(); ClearBps(); ProcessTerminateProcessCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); ProcessUnloadCmd(hprc, hthd, lpdbb); if (KdOptions[KDO_GOEXIT].value) { ContinueTargetSystem( DBG_CONTINUE, NULL ); // Hack: don't exit from here with lock held DMAPIRELEASE(); } #endif // KERNEL LpDmMsg->xosdRet = xosdNone; Reply( 0, LpDmMsg, lpdbb->hpid); break; case dmfBreakpoint: DPRINT(5,(_T("dmfBreakpoint\r\n"))); DMAPILOCK(); ProcessBreakpointCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfReadMem: // This replies in the function DPRINT(5, (_T("dmfReadMem\n"))); DMAPILOCK(); ProcessReadMemoryCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfWriteMem: DPRINT(5, (_T("dmfWriteMem\n"))); DMAPILOCK(); ProcessWriteMemoryCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfReadReg: DPRINT(5, (_T("dmfReadReg\n"))); DMAPILOCK(); ProcessGetContextCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfWriteReg: DPRINT(5, (_T("dmfWriteReg\n"))); DMAPILOCK(); ProcessSetContextCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; #ifdef HAS_DEBUG_REGS case dmfReadRegEx: DPRINT(5, (_T("dmfReadRegEx\n"))); DMAPILOCK(); #ifdef KERNEL ProcessGetExtendedContextCmd(hprc, hthd, lpdbb); #else ProcessGetDRegsCmd(hprc, hthd, lpdbb); #endif DMAPIRELEASE(); break; case dmfWriteRegEx: DPRINT(5, (_T("dmfWriteRegEx\n"))); DMAPILOCK(); #ifdef KERNEL ProcessSetExtendedContextCmd(hprc, hthd, lpdbb); #else ProcessSetDRegsCmd(hprc, hthd, lpdbb); #endif DMAPIRELEASE(); break; #else // HAS_DEBUG_REGS case dmfReadRegEx: case dmfWriteRegEx: DPRINT(5,(_T("Read/WriteRegEx\r\n"))); assert(dmf != dmfReadRegEx && dmf != dmfWriteRegEx); LpDmMsg->xosdRet = xosdUnknown; Reply( 0, LpDmMsg, lpdbb->hpid ); break; #endif // HAS_DEBUG_REGS case dmfGo: DPRINT(5, (_T("dmfGo\n"))); #if !defined(KERNEL) //Turn off fiber debugging hprc->pFbrCntx = NULL; #endif // We should not have to lock this, since it should // be a nop if the target is running anyway. ProcessContinueCmd(hprc, hthd, lpdbb); break; #if defined(KERNEL) case dmfTerm: DPRINT(5, (_T("dmfTerm\n"))); DMAPILOCK(); ProcessTerminateProcessCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; #endif case dmfStop: DPRINT(5, (_T("dmfStop\n"))); ProcessAsyncStopCmd(hprc, hthd, lpdbb); break; case dmfFreeze: DPRINT(5, (_T("dmfFreeze\n"))); ProcessFreezeThreadCmd(hprc, hthd, lpdbb); break; case dmfResume: DPRINT(5, (_T("dmfResume\n"))); ProcessAsyncGoCmd(hprc, hthd, lpdbb); break; case dmfInit: DPRINT(5, (_T("dmfInit\n"))); Reply( 0, &xosd, lpdbb->hpid); break; case dmfUnInit: DPRINT(5, (_T("dmfUnInit\n"))); #ifdef KERNEL DmPollTerminate(); #endif Reply ( 1, LpDmMsg, lpdbb->hpid); #ifndef KERNEL Cleanup(); #endif break; case dmfGetDmInfo: DPRINT(5,(_T("getDmInfo\r\n"))); ProcessGetDmInfoCmd(hprc, lpdbb, cb); break; case dmfSetupExecute: DPRINT(5, (_T("dmfSetupExecute\n"))); DMAPILOCK(); ProcessSetupExecuteCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfStartExecute: DPRINT(5, (_T("dmfStartExecute\n"))); DMAPILOCK(); ProcessStartExecuteCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfCleanUpExecute: DPRINT(5, (_T("dmfCleanupExecute\n"))); DMAPILOCK(); ProcessCleanUpExecuteCmd(hprc, hthd, lpdbb); DMAPIRELEASE(); break; case dmfSystemService: DPRINT(5, (_T("dmfSystemService\n"))); ProcessSystemServiceCmd( hprc, hthd, lpdbb ); break; case dmfDebugActive: DPRINT(5, (_T("dmfDebugActive\n"))); ProcessDebugActiveCmd( hprc, hthd, lpdbb); break; case dmfSetPath: DPRINT(5, (_T("dmfSetPath\n"))); ProcessSetPathCmd( hprc, hthd, lpdbb ); break; case dmfQueryTlsBase: DPRINT(5, (_T("dmfQueryTlsBase\n"))); ProcessQueryTlsBaseCmd(hprc, hthd, lpdbb ); break; case dmfQuerySelector: DPRINT(5, (_T("dmfQuerySelector\n"))); DMAPILOCK(); ProcessQuerySelectorCmd(hprc, hthd, lpdbb ); DMAPIRELEASE(); break; case dmfVirtualQuery: DPRINT(5,(_T("VirtualQuery\r\n"))); //DMAPILOCK(); ProcessVirtualQueryCmd(hprc, lpdbb); //DMAPIRELEASE(); break; case dmfRemoteQuit: DPRINT(5,(_T("RemoteQuit\r\n"))); ProcessRemoteQuit(); break; #ifdef KERNEL case dmfGetSections: DPRINT(5,(_T("GetGetSections\r\n"))); DMAPILOCK(); ProcessGetSectionsCmd( hprc, hthd, lpdbb ); DMAPIRELEASE(); break; #endif case dmfSetExceptionState: DPRINT(5,(_T("SetExceptionState\r\n"))); ProcessSetExceptionState(hprc, hthd, lpdbb); break; case dmfGetExceptionState: DPRINT(5,(_T("GetExceptionState\r\n"))); ProcessGetExceptionState(hprc, hthd, lpdbb); break; case dmfSingleStep: DPRINT(5,(_T("SingleStep\r\n"))); #if !defined(KERNEL) //Turn off fiber debugging hprc->pFbrCntx = NULL; #endif // again, this should not do anything unless the target is stopped, // so don't lock it. ProcessSingleStepCmd(hprc, hthd, lpdbb); break; case dmfRangeStep: DPRINT(5,(_T("RangeStep\r\n"))); #if !defined(KERNEL) //Turn off fiber debugging hprc->pFbrCntx = NULL; #endif ProcessRangeStepCmd(hprc, hthd, lpdbb); break; case dmfReturnStep: DPRINT(5,(_T("ReturnStep\r\n"))); ProcessReturnStepCmd(hprc, hthd, lpdbb); break; #ifndef KERNEL case dmfNonLocalGoto: DPRINT(5,(_T("NonLocalGoto\r\n"))); //Turn off fiber debugging hprc->pFbrCntx = NULL; ProcessNonLocalGoto(hprc, hthd, lpdbb); break; #endif case dmfThreadStatus: DPRINT(5,(_T("ThreadStatus\r\n"))); Reply( ProcessThreadStatCmd(hprc, hthd, lpdbb), LpDmMsg, lpdbb->hpid); break; case dmfProcessStatus: DPRINT(5,(_T("ProcessStatus\r\n"))); if (hprc) { LpDmMsg->xosdRet = xosdNone; Reply( ProcessProcStatCmd(hprc, hthd, lpdbb), LpDmMsg, lpdbb->hpid); } else { LpDmMsg->xosdRet = xosdBadProcess; Reply( 0, LpDmMsg, lpdbb->hpid); } break; case dmfGetTimeStamp: DPRINT(5,(_T("ProcessGetTimeStamp\r\n"))); Reply (ProcessGetTimeStamp (hprc, hthd, lpdbb), LpDmMsg, lpdbb->hpid); break; case dmfNewSymbolsLoaded: #ifdef KERNEL GetKernelSymbolAddresses(); #endif Reply( 0, LpDmMsg, lpdbb->hpid); break; default: DPRINT(5, (_T("DM: Unknown dmf == %d\n"), dmf)); assert(FALSE); break; } return; } /* DMFunc() */ #undef DMAPILOCK #undef DMAPIRELEASE #ifndef KERNEL BOOL StartDmPollThread( void ) /*++ Routine Description: This creates the DM poll thread. Arguments: none Return Value: TRUE if the thread was successfully created or already existed. --*/ { DWORD tid; if (hDmPollThread) { return TRUE; } hDmPollThread = CreateThread(0,0,CallDmPoll,0,0,&tid); return hDmPollThread != 0; } BOOL StartCrashPollThread( void ) /*++ Routine Description: This creates the DM poll thread for a crash dump file. Arguments: none Return Value: TRUE if the thread was successfully created or already existed. --*/ { DWORD tid; if (hDmPollThread) { return TRUE; } hDmPollThread = CreateThread(0,0,(LPTHREAD_START_ROUTINE)CrashDumpThread,0,0,&tid); return hDmPollThread != 0; } VOID CrashDumpThread( LPVOID lpv ) { HPRCX hprc; HTHDX hthd; HTHDX hthdNew; LPBYTE lpbPacket; WORD cbPacket; PCRASH_MODULE CrashModule; DEBUG_EVENT64 de; DWORD i; CHAR buf[32]; LPTSTR lpProgName = (LPTSTR)lpv; DWORD DumpVer = CrashDumpHeader->MajorVersion; CRASHDUMP_VERSION_INFO VersionInfo; CrashDump = TRUE; // fix up version bugs DmpDetectVersionParameters( &VersionInfo ); // simulate a create process debug event CrashModule = (PCRASH_MODULE)((PUCHAR)CrashDumpHeader+CrashDumpHeader->ModuleOffset); de.dwDebugEventCode = CREATE_PROCESS_DEBUG_EVENT; de.dwProcessId = 1; de.dwThreadId = 1; de.u.CreateProcessInfo.hFile = NULL; de.u.CreateProcessInfo.hProcess = NULL; de.u.CreateProcessInfo.hThread = NULL; de.u.CreateProcessInfo.lpBaseOfImage = CrashModule->BaseOfImage; de.u.CreateProcessInfo.dwDebugInfoFileOffset = 0; de.u.CreateProcessInfo.nDebugInfoSize = 0; de.u.CreateProcessInfo.lpStartAddress = 0; de.u.CreateProcessInfo.lpThreadLocalBase = 0; if (!CrashModule->ImageName[0]) { de.u.CreateProcessInfo.lpImageName = SEPtrTo64("mod0.dll"); } else { de.u.CreateProcessInfo.lpImageName = SEPtrTo64(CrashModule->ImageName); } _ftcscpy( nameBuffer, (PVOID)de.u.CreateProcessInfo.lpImageName); de.u.CreateProcessInfo.fUnicode = FALSE; ProcessDebugEvent(&de); WaitForSingleObject( hEventCreateProcess, INFINITE ); // wait for the shell to say go to the thread WaitForSingleObject( hEventContinue, INFINITE ); // mark the process as 'being connected' so that the continue debug // events that are received from the shell are ignored hprc = HPRCFromPID(1); hthd = hprc->hthdChild; hprc->pstate |= ps_connect; // Get rid of the expected breakpoint ConsumeAllProcessEvents(hprc, FALSE); if (DumpVer >= 4) { DmpGetThread( 0, &hthd->CrashThread ); hthd->offTeb = hthd->CrashThread.Teb; } else { ZeroMemory(&hthd->CrashThread, sizeof(CRASH_THREAD)); hthd->CrashThread.ThreadId = hthd->tid; } // generates the mod load events for (i=1; iModuleCount; i++) { // drwtsn32 puts a bogus value in the name length field // when the string is empty: int namelen = CrashModule->ImageName[0] ? CrashModule->ImageNameLength : 1; CrashModule = (PCRASH_MODULE) ( (PUCHAR)CrashModule + sizeof(CRASH_MODULE) + namelen ); de.dwDebugEventCode = LOAD_DLL_DEBUG_EVENT; de.dwProcessId = 1; de.dwThreadId = 1; de.u.LoadDll.hFile = NULL; // Sign extend..... if (sizeof(CrashModule->BaseOfImage) == sizeof(DWORD64)) { de.u.LoadDll.lpBaseOfDll = CrashModule->BaseOfImage; } else { de.u.LoadDll.lpBaseOfDll = SE32To64(CrashModule->BaseOfImage); } if (!CrashModule->ImageName[0]) { sprintf( buf, "mod%d.dll", i ); de.u.LoadDll.lpImageName = SEPtrTo64(buf); } else { de.u.LoadDll.lpImageName = SEPtrTo64(CrashModule->ImageName); } de.u.LoadDll.fUnicode = FALSE; de.u.LoadDll.nDebugInfoSize = 0; de.u.LoadDll.dwDebugInfoFileOffset = 0; if (LoadDll(&de,hthd,&cbPacket,&lpbPacket, FALSE) && (cbPacket != 0)) { NotifyEM(&de, hthd, cbPacket, (ULONG64)lpbPacket); } } // generate a load complete debug event ResetEvent( hEventContinue ); de.dwProcessId = hprc->pid; de.dwThreadId = hprc->hthdChild->tid; de.dwDebugEventCode = LOAD_COMPLETE_DEBUG_EVENT; NotifyEM( &de, hthd, 0, 0L); WaitForSingleObject( hEventContinue, INFINITE ); // create all of the threads for (i=1; iThreadCount; i++) { // generate a thread create event ResetEvent( hprc->hEventCreateThread ); ResetEvent( hEventContinue ); de.dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = i + 1; de.u.CreateThread.hThread = (HANDLE) (i + 1); de.u.CreateThread.lpThreadLocalBase = 0; de.u.CreateThread.lpStartAddress = 0; // Take critical section here so that it is still // held after leaving ProcessDebugEvent EnterCriticalSection(&csProcessDebugEvent); ProcessDebugEvent(&de); hthdNew = HTHDXFromPIDTID(1,(i+1)); DbgGetThreadContext( hthdNew, &hthdNew->context ); if (DumpVer >= 4) { DmpGetThread( i, &hthdNew->CrashThread ); hthdNew->offTeb = hthdNew->CrashThread.Teb; } else { ZeroMemory(&hthdNew->CrashThread, sizeof(CRASH_THREAD)); hthdNew->CrashThread.ThreadId = hthdNew->tid; } LeaveCriticalSection(&csProcessDebugEvent); WaitForSingleObject( hprc->hEventCreateThread, INFINITE ); // wait for the shell to continue the new thread WaitForSingleObject( hEventContinue, INFINITE ); } // Get the debug event for the crash if (64 == VersionInfo.PointerSize) { de = *(LPDEBUG_EVENT64)((PUCHAR)CrashDumpHeader+CrashDumpHeader->DebugEventOffset); // convert the thread and process ids into the internal version... de.dwProcessId = 1; for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) { if (de.dwThreadId == hthd->CrashThread.ThreadId) { de.dwThreadId = hthd->tid; break; } } } else { // if this is an old crashdump file, try to find the crashed thread de.dwDebugEventCode = EXCEPTION_DEBUG_EVENT; // BUGBUG kentf no support yet for 64 bit crashdump ExceptionRecord32To64((PEXCEPTION_RECORD32)CrashException, &de.u.Exception.ExceptionRecord); for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) { if (PC(hthd) == de.u.Exception.ExceptionRecord.ExceptionAddress) { de.dwThreadId = hthd->tid; } } } ProcessDebugEvent( &de ); while (!fDmPollQuit) { // Handle kill commands if (KillQueue) { CompleteTerminateProcessCmd(); } if (ReloadStruct.Flag) { ReloadUsermodeModules(ReloadStruct.Hthd, ReloadStruct.String); free((PVOID)ReloadStruct.String); ReloadStruct.Flag = 0; } Sleep( 500 ); } } DWORD WINAPI CallDmPoll(LPVOID lpv) /*++ Routine Description: This is the debug event loop. This routine creates or attaches to child process, and monitors them for debug events. It serializes the dispatching of events from multiple process, and continues the events when the worker functions have finished processing the events. Arguments: lpv - Supplies an argument provided by CreateThread. --*/ { DEBUG_EVENT64 de; PROCESS_INFORMATION pi; int nprocs = 0; UINT ErrMode; Unreferenced( lpv ); DPRINT(5,(_T("CallDmPoll\r\n"))); // Crank up priority to improve performance, and improve our // chances of winning races with the debuggee. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); fDmPollQuit = FALSE; while (!fDmPollQuit) { // Handle kill commands if (KillQueue) { CompleteTerminateProcessCmd(); goto doContinues; } // Handle spawn commands if (SpawnStruct.fSpawn) { SpawnStruct.fSpawn = FALSE; ErrMode = SetErrorMode( 0 ); SpawnStruct.fReturn = CreateProcess( SpawnStruct.szAppName, SpawnStruct.szArgs, NULL, NULL, SpawnStruct.fInheritHandles, SpawnStruct.fdwCreate, NULL, SpawnStruct.pszCurrentDirectory, &SpawnStruct.si, &pi); SetErrorMode( ErrMode ); if (SpawnStruct.fReturn == 0) { SpawnStruct.dwError = GetLastError(); } else { SpawnStruct.dwError = 0; CloseHandle(pi.hThread); CloseHandle(pi.hProcess); Close3Handles(rgh); } SetEvent(SpawnStruct.hEventApiDone); } // Handle attach commands if (DebugActiveStruct.fAttach) { if (fDisconnected && DebugActiveStruct.dwProcessId == 0) { SetEvent( hEventRemoteQuit ); SetEvent( DebugActiveStruct.hEventReady ); DebugActiveStruct.fReturn = 1; DebugActiveStruct.dwError = 0; } else { DebugActiveStruct.fAttach = FALSE; SetDebugPrivilege (); DebugActiveStruct.fReturn = DebugActiveProcess(DebugActiveStruct.dwProcessId); if (DebugActiveStruct.fReturn == 0) { DebugActiveStruct.dwError = GetLastError(); } else { DebugActiveStruct.dwError = 0; } } SetEvent(DebugActiveStruct.hEventApiDone); } if (WtStruct.fWt) { WtStruct.fWt = FALSE; switch(WtStruct.dwType) { case IG_WATCH_TIME_STOP: WtStruct.hthd->wtmode = 2; break; case IG_WATCH_TIME_RECALL: break; case IG_WATCH_TIME_PROCS: break; } } if (ReloadStruct.Flag) { ReloadUsermodeModules(ReloadStruct.Hthd, ReloadStruct.String); free((PVOID)ReloadStruct.String); ReloadStruct.Flag = 0; } if (DMWaitForDebugEvent(&de, (DWORD) WAITFORDEBUG_MS)) { #ifndef KERNEL { PDETOSAVE pDeToSave = GetNextFreeDebugEvent(); pDeToSave->de = de; InsertTailList(&DmListOfPendingDebugEvents, &pDeToSave->List); } #endif // !KERNEL if ( de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT ) { assert(HPRCFromPID(de.dwProcessId) == NULL); // post a dummy message to the app so it will unblock (W95) if (IsChicago() && DebugActiveStruct.fReturn) { PostThreadMessage (de.dwThreadId, WM_NULL, 0, 0); } if (nprocs == 0) { ResetEvent(hEventNoDebuggee); } ++nprocs; } else if ( de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) { --nprocs; } if (fDisconnected) { if (de.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT || de.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT || de.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT || de.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT ) { // we can process these debug events very carefully // while disconnected from the shell. the only requirement // is that the dm doesn't call NotifyEM while disconnected. } else #if 0 if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { // if the exception is to be ignored, we can handle it here. // if not, we have to wait for a reconnect. } else #endif { WaitForSingleObject( hEventRemoteQuit, INFINITE ); ResetEvent( hEventRemoteQuit ); fDisconnected = FALSE; // this is a remote session reconnecting ReConnectDebugger( &de, FALSE ); } } ProcessDebugEvent(&de); } else if (WaitForSingleObject( hEventRemoteQuit, 0 ) == WAIT_OBJECT_0) { // this is a remote session reconnecting ResetEvent( hEventRemoteQuit ); ReConnectDebugger( NULL, FALSE ); } else if (nWaitingForLdrBreakpoint) { // look for processes that are looking for a loader bp. // See how long it has been since they got an event. HPRCX hprc; BOOL Exited = FALSE; EnterCriticalSection(&csThreadProcList); for (hprc = prcList->next; hprc; hprc = hprc->next) { if (hprc->pstate & ps_preStart) { if (++hprc->cLdrBPWait > LDRBP_MAXTICKS) { // Signal a timeout for this one. // just jump out of this loop - if // another one is going to time out, // it can do it on the next pass. break; // See if a pending attach has failed because the target process // exited before the debugger got hooked up. } else if (WaitForSingleObject(DebugActiveStruct.hEventReady, 0) != 0 && hprc->pid == DebugActiveStruct.dwProcessId && DebugActiveStruct.hProcess != INVALID_HANDLE_VALUE && WaitForSingleObject(DebugActiveStruct.hProcess, 0) == 0) { // An attached process exited before it really got attached Exited = TRUE; break; } } } LeaveCriticalSection(&csThreadProcList); if (hprc) { HandleDebugActiveDeadlock(hprc, Exited); } } else if (nprocs == 0) { Sleep(WAITFORDEBUG_MS); } doContinues: if (DequeueAllEvents(TRUE, FALSE) && nprocs <= 0) { SetEvent(hEventNoDebuggee); } } DEBUG_PRINT(_T("CallDmPoll Exit\r\n")); return 0; } /* CallDmPoll() */ BOOL SetDebugPrivilege(void) /*++ Routine Description: Enables SeDebugPrivilege if possible. Return Value: TRUE on success, FALSE on failure --*/ { HANDLE hToken; LUID DebugValue; TOKEN_PRIVILEGES tkp; BOOL rVal = TRUE; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { return FALSE; } if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &DebugValue)) { rVal = FALSE; } else { tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = DebugValue; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if (GetLastError() != ERROR_SUCCESS) { rVal = FALSE; } } CloseHandle(hToken); return rVal; } #endif // !KERNEL /* Dll Version */ #ifdef KERNEL #ifdef DEBUGVER DEBUG_VERSION('D','M',"NT Kernel Debugger Monitor") #else RELEASE_VERSION('D','M',"NT Kernel Debugger Monitor") #endif #else // KERNEL #ifdef DEBUGVER DEBUG_VERSION('D','M',"WIN32 Debugger Monitor") #else RELEASE_VERSION('D','M',"WIN32 Debugger Monitor") #endif #endif // KERNEL DBGVERSIONCHECK() int WINAPI DllMain( HANDLE hModule, DWORD dwReason, DWORD dwReserved ) /*++ Routine Description: Entry point called by the loader during DLL initialization and deinitialization. This creates and destroys some per- instance objects. Arguments: hModule - Supplies base address of dll dwReason - Supplies flags describing why we are here dwReserved - depends on dwReason. Return Value: TRUE --*/ { Unreferenced(hModule); Unreferenced(dwReserved); #ifndef KERNEL InitializeListHead(&DmListOfPendingDebugEvents); InitializeListHead(&DmFreeListOfDebugEvents); #endif // KERNEL switch (dwReason) { case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: CloseHandle(SpawnStruct.hEventApiDone); CloseHandle(hEventCreateProcess); CloseHandle(hEventRemoteQuit); CloseHandle(hEventNoDebuggee); CloseHandle(hEventContinue); DeleteCriticalSection(&csProcessDebugEvent); DeleteCriticalSection(&csThreadProcList); DeleteCriticalSection(&csEventList); DeleteCriticalSection(&csWalk); #if !defined(KERNEL) CloseHandle(DebugActiveStruct.hEventApiDone); CloseHandle(DebugActiveStruct.hEventReady); DeleteCriticalSection(&csKillQueue); #endif break; case DLL_PROCESS_ATTACH: InitializeCriticalSection(&csProcessDebugEvent); InitializeCriticalSection(&csThreadProcList); InitializeCriticalSection(&csEventList); InitializeCriticalSection(&csWalk); hEventCreateProcess = CreateEvent(NULL, TRUE, FALSE, NULL); hEventRemoteQuit = CreateEvent(NULL, TRUE, FALSE, NULL); hEventContinue = CreateEvent(NULL, TRUE, FALSE, NULL); hEventNoDebuggee = CreateEvent(NULL, TRUE, FALSE, NULL); SpawnStruct.hEventApiDone = CreateEvent(NULL, TRUE, FALSE, NULL); #if defined(KERNEL) InitializeCriticalSection(&csApiInterlock); InitializeCriticalSection(&csSynchronizeTargetInterlock); #endif // KERNEL #if !defined(KERNEL) InitializeCriticalSection(&csKillQueue); DebugActiveStruct.hEventApiDone = CreateEvent(NULL, TRUE, TRUE, NULL); DebugActiveStruct.hEventReady = CreateEvent(NULL, TRUE, TRUE, NULL); /* * These parameters are from SCOTTLU */ SetProcessShutdownParameters(0x3ff, 0); // get helpful info hInstance = hModule; GetSystemInfo(&SystemInfo); OsVersionInfo.dwOSVersionInfoSize = sizeof(OsVersionInfo); GetVersionEx(&OsVersionInfo); #if defined (TARGET_i386) FChicago = ((GetVersion() & 0x80000000) && ((GetVersion() & 0xff)>3)); #endif // TARGET_i386 #endif // !KERNEL break; } return TRUE; } BOOL PASCAL IsChicago(VOID) /*++ Routine Description: This routine allows the caller to determine if the DM is running on the Win95 family Return Value: TRUE if running on Win95 and FALSE othewise. --*/ { return FChicago; } BOOL PASCAL DmDllInit( LPDBF lpb ) /*++ Routine Description: This routine allows the shell (debugger or remote stub) to provide a service callback vector to the DM. Arguments: lpb - Supplies an array of functions for callbacks Return Value: TRUE if successfully initialized and FALSE othewise. --*/ { lpdbf = lpb; return TRUE; } /* DmDllInit() */ #ifdef KERNEL void ParseDmParams( LPTSTR p ) { DWORD i; CHAR szPath[MAX_PATH]; CHAR szStr[_MAX_PATH]; LPTSTR lpPathNext; LPTSTR lpsz1; LPTSTR lpsz2; LPTSTR lpsz3; for (i=0; i 1) { nVerbose = (BOOL)KdOptions[KDO_VERBOSE].value; } else { nVerbose = MIN_VERBOSITY_LEVEL; } szPath[0] = 0; lpPathNext = _ftcstok((LPTSTR)KdOptions[KDO_SYMBOLPATH].value, _T(";")); while (lpPathNext) { lpsz1 = szStr; while ((lpsz2 = _ftcschr(lpPathNext, _T('%'))) != NULL) { _ftcsncpy(lpsz1, lpPathNext, (size_t)(lpsz2 - lpPathNext)); lpsz1 += lpsz2 - lpPathNext; lpsz2++; lpPathNext = _ftcschr(lpsz2, _T('%')); if (lpPathNext != NULL) { *lpPathNext++ = 0; lpsz3 = getenv(lpsz2); if (lpsz3 != NULL) { _ftcscpy(lpsz1, lpsz3); lpsz1 += _ftcslen(lpsz3); } } else { lpPathNext = _T(""); } } _ftcscpy(lpsz1, lpPathNext); _ftcscat( szPath, szStr ); _ftcscat( szPath, _T(";") ); lpPathNext = _ftcstok(NULL, _T(";")); } if ( szPath[0] != 0 ) { if (szPath[_ftcslen(szPath)-1] == _T(';')) { szPath[_ftcslen(szPath)-1] = _T('\0'); } _ftcscpy( (LPTSTR)KdOptions[KDO_SYMBOLPATH].value, szPath ); } } #endif // KERNEL VOID ProcessRemoteQuit(VOID) { HPRCX hprc; PBREAKPOINT pbp; PBREAKPOINT pbpT; EnterCriticalSection(&csThreadProcList); for (hprc=prcList->next; hprc; hprc=hprc->next) { for (pbp = BPNextHprcPbp(hprc, NULL); pbp; pbp = pbpT) { pbpT = BPNextHprcPbp(hprc, pbp); RemoveBP(pbp); } } LeaveCriticalSection(&csThreadProcList); fDisconnected = TRUE; ResetEvent( hEventRemoteQuit ); } XOSD FAR PASCAL DMInit( DMTLFUNCTYPE lpfnTl, LPTSTR lpch ) /*++ Routine Description: This is the entry point called by the TL to initialize the connection from DM to TL. Arguments: lpfnTl - Supplies entry point to TL lpch - Supplies command line arg list Return Value: XOSD value: xosdNone for success, other values reflect reason for failure to initialize properly. --*/ { int i, n; XOSD xosd; DEBUG_PRINT(_T("DMInit\r\n")); if (lpfnTl != NULL) { /* ** Parse out anything interesting from the command line args */ while (*lpch) { while (isspace(*lpch)) { lpch++; } if (*lpch != _T('/') && *lpch != _T('-')) { break; } lpch++; switch (*lpch++) { case 0: // don't skip over end of string --lpch; default: // assert, continue is ok. assert(FALSE); break; case 'v': case 'V': while (isspace(*lpch)) { lpch++; } nVerbose = atoi(lpch); while (isdigit(*lpch)) { lpch++; } break; case 'r': case 'R': FDMRemote = TRUE; break; case 'd': case 'D': FUseOutputDebugString = TRUE; break; } } #ifdef KERNEL ParseDmParams( lpch ); #endif /* Define a false single step event */ falseSSEvent.dwDebugEventCode = EXCEPTION_DEBUG_EVENT; falseSSEvent.u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP; falseBPEvent.dwDebugEventCode = BREAKPOINT_DEBUG_EVENT; falseBPEvent.u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_BREAKPOINT; FuncExitEvent.dwDebugEventCode = FUNC_EXIT_EVENT; FuncExitEvent.u.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP; /* Define the standard notification method */ EMNotifyMethod.notifyFunction = ConsumeThreadEventsAndNotifyEM; EMNotifyMethod.lparam = 0; SearchPathString[0] = _T('\0'); SearchPathSet = FALSE; InitEventQueue(); // initialize data breakpoint handler ExprBPInitialize(); SetDebugErrorLevel(SLE_WARNING); /* ** Save the pointer to the Transport layer entry function */ DmTlFunc = lpfnTl; /* ** Try and connect up to the other side of the link */ DmTlFunc( tlfSetBuffer, NULL, sizeof(abEMReplyBuf), (LPARAM) abEMReplyBuf ); if ((xosd = DmTlFunc( tlfConnect, NULL, 0, 0)) != xosdNone ) { return(xosd); } DPRINT(10, (_T("DM & TL are now connected\n"))); } else { DmTlFunc( tlfDisconnect, NULL, 0, 0); DmTlFunc( tlfSetBuffer, NULL, 0, 0); FDMRemote = FALSE; DmTlFunc = (DMTLFUNCTYPE) NULL; } return xosdNone; } /* DmInit() */ #ifndef KERNEL VOID Cleanup(VOID) /*++ Routine Description: Cleanup of DM, prepare for exit. --*/ { HTHDX pht, phtt; HPRCX php, phpt; BREAKPOINT *bp, *bpt; int iDll; /* Free all threads and close their handles */ for (pht = thdList->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse; pht; pht = phtt) { phtt = pht->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse; if (pht->rwHand != (HANDLE)INVALID) { CloseHandle(pht->rwHand); } MHFree(pht); } thdList->nextGlobalThreadThisProbablyIsntTheOneYouWantedToUse = NULL; /* Free all processes and close their handles */ for(php = prcList->next; php; php = phpt) { phpt = php->next; RemoveExceptionList(php); // Free any fibers that may be left RemoveFiberList(php); for (iDll = 0; iDll < php->cDllList; iDll++) { DestroyDllLoadItem(&php->rgDllList[iDll]); } MHFree(php->rgDllList); if (php->rwHand != (HANDLE)INVALID) { CloseHandle(php->rwHand); } CloseHandle(php->hExitEvent); CloseHandle(php->hEventCreateThread); MHFree(php); } prcList->next = NULL; /* Free all breakpoints */ for(bp = bpList->next; bp; bp = bpt) { bpt = bp->next; MHFree(bp); } bpList->next = NULL; // Ask the disassembler to clean itself up. CleanupDisassembler( ); if (hDmPollThread) { fDmPollQuit = TRUE; WaitForSingleObject(hDmPollThread, INFINITE); hDmPollThread = 0; } while (!IsListEmpty(&DmListOfPendingDebugEvents)) { PLIST_ENTRY p = RemoveHeadList(&DmListOfPendingDebugEvents); MHFree(p); } while (!IsListEmpty(&DmFreeListOfDebugEvents)) { PLIST_ENTRY p = RemoveHeadList(&DmFreeListOfDebugEvents); MHFree(p); } } #endif // !KERNEL BOOL WINAPIV DMPrintShellMsg( LPTSTR szFormat, ... ) /*++ Routine Description: This function prints a string on the shell's command window. Arguments: szFormat - Supplies format string for sprintf ... - Supplies variable argument list Return Value: TRUE -> all is ok and the string was printed FALSE -> something's hosed and no string printed --*/ { TCHAR buf[512]; DWORD bufLen; va_list marker; LPINFOAVAIL lpinf; LPRTP lprtp = NULL; BOOL rVal = TRUE; va_start( marker, szFormat ); bufLen = _vsnprintf(buf, sizeof(buf), szFormat, marker ); va_end( marker); if (bufLen == -1) { buf[sizeof(buf) - 1] = _T('\0'); } __try { if (!DmTlFunc || (HPID)INVALID == hpidRoot) { // If we can't print it to the shell, hopefully to an // attached debugger. OutputDebugString(buf); } else { bufLen = _ftcslen(buf) + 1; lprtp = (LPRTP) MHAlloc( FIELD_OFFSET(RTP, rgbVar)+sizeof(INFOAVAIL)+bufLen ); lpinf = (LPINFOAVAIL)(lprtp->rgbVar); lprtp->dbc = dbcInfoAvail; lprtp->hpid = hpidRoot; lprtp->htid = NULL; lprtp->cb = (int)bufLen; lpinf->fReply = FALSE; lpinf->fUniCode = FALSE; memcpy( lpinf->buffer, buf, bufLen ); DmTlFunc( tlfDebugPacket, lprtp->hpid, (FIELD_OFFSET(RTP, rgbVar)+sizeof(INFOAVAIL)+bufLen), (LPARAM) lprtp ); } } __except(EXCEPTION_EXECUTE_HANDLER) { rVal = FALSE; } if (lprtp) { MHFree( lprtp ); } return rVal; } VOID WINAPIV DebugPrint(LPTSTR szFormat, ...) { TCHAR rgchDebug[1024]; va_list marker; int n; va_start( marker, szFormat ); n = _vsnprintf(rgchDebug, _tsizeof(rgchDebug), szFormat, marker ); va_end( marker); if (n == -1) { rgchDebug[_tsizeof(rgchDebug)-1] = 0; } OutputDebugString( rgchDebug ); return; } /* DebugPrint() */ int pCharMode( LPTSTR szAppName, PIMAGETYPE pImageType ) /*++ Routine Description: This routine is used to determine the type of exe which we are going to be debugging. This is decided by looking for exe headers and making decisions based on the information in the exe headers. Arguments: szAppName - Supplies the path to the debugger exe pImageType - Returns the type of the image Return Value: System_Invalid - could not find the exe file System_GUI - GUI application System_Console - console application --*/ { IMAGE_DOS_HEADER dosHdr; IMAGE_OS2_HEADER os2Hdr; IMAGE_NT_HEADERS ntHdr; DWORD cb; HANDLE hFile; int ret; BOOL GotIt; _tcscpy(nameBuffer, szAppName); // don't use OpenFile as it fails paths >127 bytes long hFile = CreateFile( szAppName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == (HANDLE)-1) { /* * Could not open file! */ DEBUG_PRINT_2(_T("CreateFile(%s) --> %u\r\n"), szAppName, GetLastError()); return System_Invalid; } /* * Try and read an MZ Header. If you can't then it can not possibly * be a legal exe file. (Not strictly true but we will ignore really * short com files since they are unintersting). */ SetFilePointer(hFile, 0, NULL, FILE_BEGIN); if ((!ReadFile(hFile, &dosHdr, sizeof(dosHdr), &cb, NULL)) || (cb != sizeof(dosHdr))) { if (_ftcsicmp(&szAppName[_ftcslen(szAppName) - 4], _T(".COM")) == 0) { *pImageType = Image_16; } else { DPRINT(1, (_T("dosHdr problem.\n"))); *pImageType = Image_Unknown; } CloseHandle(hFile); return System_GUI; } /* * Verify the MZ header. * NOTENOTE Messup the case of no MZ header. */ if (dosHdr.e_magic != IMAGE_DOS_SIGNATURE) { /* * We did not start with the MZ signature. If the extension * is .COM then it is a COM file. */ if (_ftcsicmp(&szAppName[_ftcslen(szAppName) - 4], _T(".COM")) == 0) { *pImageType = Image_16; } else { DPRINT(1, (_T("MAGIC problem(MZ).\n"))); *pImageType = Image_Unknown; } CloseHandle(hFile); #ifndef KERNEL if (DmpInitialize( szAppName, &CrashContext, &CrashException, &CrashDumpHeader )) { // Verify the architecture if ( #if defined(TARGET_i386) CrashDumpHeader->MachineImageType != IMAGE_FILE_MACHINE_I386 #elif defined(TARGET_ALPHA) CrashDumpHeader->MachineImageType != IMAGE_FILE_MACHINE_ALPHA #elif defined(TARGET_AXP64) CrashDumpHeader->MachineImageType != IMAGE_FILE_MACHINE_AXP64 #elif defined(TARGET_IA64) CrashDumpHeader->MachineImageType != IMAGE_FILE_MACHINE_IA64 #else #error( "unknown target machine" ); #endif ) { return System_DmpWrongPlatform; } // User mode validation if (DmpIsItAUserModeFile()) { if (!DmpUserModeTestHeader()) { return System_DmpInvalid; } } *pImageType = Image_Dump; } #endif // !KERNEL return System_Console; } if ( dosHdr.e_lfanew == 0 ) { /* * Straight DOS exe. */ DPRINT(1, (_T("[DOS image].\n"))); *pImageType = Image_16; CloseHandle(hFile); return System_Console; } /* * Now look at the next EXE header (either NE or PE) */ SetFilePointer(hFile, dosHdr.e_lfanew, NULL, FILE_BEGIN); GotIt = FALSE; ret = System_GUI; /* * See if this is a Win16 program */ if (ReadFile(hFile, &os2Hdr, sizeof(os2Hdr), &cb, NULL) && (cb == sizeof(os2Hdr))) { if ( os2Hdr.ne_magic == IMAGE_OS2_SIGNATURE ) { /* * Win16 program (may be an OS/2 exe also) */ DPRINT(1, (_T("[Win16 image].\n"))); *pImageType = Image_16; GotIt = TRUE; } else if ( os2Hdr.ne_magic == IMAGE_OS2_SIGNATURE_LE ) { /* * OS2 program - Not supported */ DPRINT(1, (_T("[OS/2 image].\n"))); *pImageType = Image_Unknown; GotIt = TRUE; } } /* * If the above failed, see if it is an NT program */ if ( !GotIt ) { SetFilePointer(hFile, dosHdr.e_lfanew, NULL, FILE_BEGIN); if (ReadFile(hFile, &ntHdr, sizeof(ntHdr), &cb, NULL) && (cb == sizeof(ntHdr)) && (ntHdr.Signature == IMAGE_NT_SIGNATURE)) { /* * All CUI (Character user interface) subsystems * have the lowermost bit set. */ DPRINT(1, ((ntHdr.OptionalHeader.Subsystem & 1) ? _T("[*Character mode app*]\n") : _T("[*Windows mode app*]\n"))); ret = ((ntHdr.OptionalHeader.Subsystem & 1)) ? System_Console : System_GUI; *pImageType = Image_32; } else { DWORD FileSize; FileSize = SetFilePointer(hFile, 0, NULL, FILE_END); if ( (DWORD)dosHdr.e_lfanew > FileSize ) { // Bogus e_lfanew, assume DOS DPRINT(1, (_T("[DOS image assumed].\n"))); *pImageType = Image_16; ret = System_Console; } else { // Not an NT image. DPRINT(1, (_T("MAGIC problem(PE).\n"))); *pImageType = Image_Unknown; } } } CloseHandle(hFile); return ret; } /* pCharMode() */ VOID ReConnectDebugger( DEBUG_EVENT64 *lpde, BOOL fNoDllLoad ) /*++ Routine Description: This function handles the case where the dm/tl is re-connected to a debugger. This function must re-instate the debugger to the correct state that existed before the disconnect action. (wesw) 11-3-93 --*/ { DWORD i; DEBUG_EVENT64 de; HPRCX hprc; HTHDX hthd; HTHDX hthd_lb; DWORD id; HANDLE hThread; BOOL fException = FALSE; // the dm is now connected fDisconnected = FALSE; // check to see if a re-connection is occurring while the // process is running or after a non-servicable debug event if (lpde) { hthd = HTHDXFromPIDTID((PID)lpde->dwProcessId,(TID)lpde->dwThreadId); if (hthd) { if (lpde->dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { if (lpde->u.Exception.dwFirstChance) { hthd->tstate |= ts_first; } else { hthd->tstate |= ts_second; } } hthd->tstate &= ~ts_running; hthd->tstate |= ts_stopped; } } fUseRoot = TRUE; for (hprc = prcList->next; hprc; hprc = hprc->next) { // generate a create process event if (fUseRoot) { hprc->pstate |= ps_root; hprc->hpid = hpidRoot; fUseRoot = FALSE; } else { hprc->hpid = (HPID)INVALID; } assert( Is64PtrSE(hprc->rgDllList[0].offBaseOfImage) ); hthd=hprc->hthdChild; ResetEvent(hEventCreateProcess); de.dwDebugEventCode = CREATE_PROCESS_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; de.u.CreateProcessInfo.hFile = NULL; de.u.CreateProcessInfo.hProcess = hprc->rwHand; de.u.CreateProcessInfo.hThread = hthd->rwHand; de.u.CreateProcessInfo.lpBaseOfImage = hprc->rgDllList[0].offBaseOfImage; de.u.CreateProcessInfo.dwDebugInfoFileOffset = 0; de.u.CreateProcessInfo.nDebugInfoSize = 0; de.u.CreateProcessInfo.lpStartAddress = PC(hthd); de.u.CreateProcessInfo.lpThreadLocalBase = 0; de.u.CreateProcessInfo.lpImageName = 0; de.u.CreateProcessInfo.fUnicode = 0; NotifyEM(&de, hthd, 0, (ULONG64)hprc); WaitForSingleObject(hEventCreateProcess, INFINITE); // mark the process as 'being connected' so that the continue debug // events that are received from the shell are ignored hprc->pstate |= ps_connect; // look for a thread that is stopped and not dead for (hthd=hprc->hthdChild,hthd_lb=NULL; hthd; hthd=hthd->nextSibling) { if ((!(hthd->tstate & ts_dead)) && (hthd->tstate & ts_stopped)) { hthd_lb = hthd; break; } } if (hthd_lb == NULL) { // if we get here then there are no threads that are stopped // so we must look for the first alive thread for (hthd=hprc->hthdChild,hthd_lb=NULL; hthd; hthd=hthd->nextSibling) { if (!(hthd->tstate & ts_dead)) { hthd_lb = hthd; break; } } } if (hthd_lb == NULL) { // if this happens then we are really screwed. there are no valid // threads to use, so lets bail out. return; } if ((hthd_lb->tstate & ts_first) || (hthd_lb->tstate & ts_second)) { fException = TRUE; } // generate mod loads for all the dlls for this process // this MUST be done before the thread creates because the // current PC of each thread can be in any of the loaded // modules. hthd = hthd_lb; if (!fNoDllLoad) { for (i=0; i<(DWORD)hprc->cDllList; i++) { if (hprc->rgDllList[i].fValidDll) { LPBYTE lpbPacket; WORD cbPacket; assert( Is64PtrSE(hprc->rgDllList[i].offBaseOfImage) ); de.dwDebugEventCode = LOAD_DLL_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; de.u.LoadDll.hFile = NULL; de.u.LoadDll.lpBaseOfDll = hprc->rgDllList[i].offBaseOfImage; de.u.LoadDll.lpImageName = (ULONG64)hprc->rgDllList[i].szDllName; de.u.LoadDll.fUnicode = FALSE; if (LoadDll(&de, hthd, &cbPacket, &lpbPacket, FALSE) || (cbPacket == 0)) { NotifyEM(&de, hthd, cbPacket, (ULONG64)lpbPacket); } } } } // loop thru all the threads for this process and // generate a thread create event for each one for (hthd=hprc->hthdChild; hthd; hthd=hthd->nextSibling) { if (!(hthd->tstate & ts_dead)) { if (fException && hthd_lb == hthd) { continue;// do this one last } // generate a thread create event ResetEvent( hprc->hEventCreateThread ); ResetEvent( hEventContinue ); de.dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; NotifyEM( &de, hthd, 0, (ULONG64)hprc ); WaitForSingleObject( hprc->hEventCreateThread, INFINITE ); WaitForSingleObject( hEventContinue, INFINITE );// wait for the shell to continue the new thread } } hthd = hthd_lb; if (fException) { // We saved this thread for last - // generate a thread create event ResetEvent( hprc->hEventCreateThread ); ResetEvent( hEventContinue ); de.dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; NotifyEM( &de, hthd, 0, (ULONG64)hprc ); WaitForSingleObject( hprc->hEventCreateThread, INFINITE ); WaitForSingleObject( hEventContinue, INFINITE );// wait for the shell to continue the new thread } if (hthd->tstate & ts_running) { // we didn't have a stopped thread - // this will create a thread in the debuggee that will immediately stop at a breakpoint. // this will cause the shell to think that we are processing a normal attach. HMODULE hModule = GetModuleHandle("ntdll.dll"); FARPROC ProcAddr = GetProcAddress(hModule, "DbgBreakPoint" ); RegisterExpectedEvent( hprc, (HTHDX)NULL, BREAKPOINT_DEBUG_EVENT, NO_SUBCLASS, DONT_NOTIFY, ActionDebugActiveReady, FALSE, (ULONG64)hprc); hThread = CreateRemoteThread( (HANDLE) hprc->rwHand, NULL, 4096, (LPTHREAD_START_ROUTINE) ProcAddr, 0, 0, &id); } else if (!lpde || lpde->dwThreadId != hthd->tid) { // generate a load complete event ResetEvent( hEventContinue ); de.dwDebugEventCode = LOAD_COMPLETE_DEBUG_EVENT; de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; NotifyEM( &de, hthd, 0, 0L); WaitForSingleObject( hEventContinue, INFINITE );// wait for the continue... if (hthd->tstate & ts_stopped) { // deliver the exception. de.dwProcessId = hprc->pid; de.dwThreadId = hthd->tid; if ((hthd->tstate & ts_first) || (hthd->tstate & ts_second)) { de.dwDebugEventCode = EXCEPTION_DEBUG_EVENT; } else { de.dwDebugEventCode = BREAKPOINT_DEBUG_EVENT; } de.u.Exception.dwFirstChance = (hthd->tstate & ts_first) != 0; de.u.Exception.ExceptionRecord = hthd->ExceptionRecord; NotifyEM(&de, hthd, 0, 0); } } hprc->pstate &= ~ps_connect;// reset the process state } } /* * HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK * In NT 3.x, it is impossible for a GUI app to spawn a console * app and have one or two of the console app's standard handles * redirected away from the console; you can only do it with * either none of the handles redirected, or all three of them. * See also the struct STARTUPINFO in the Win32 Programmer's Reference * However, I learned by experimentation that it just so happens * that there are a few magic values I can pass in for stdin, * stdout, and stderr, which will have the desired effect of * leaving that handle attached to the console. For example, * if I pass ((HANDLE)3) for stdin, stdin will stay attached * to the console. stdout is ((HANDLE)7), and stderr is * ((HANDLE)11) * In UNIX, you would probably use 0, 1, and 2. Perhaps it is no * conicidence that for x={0, 1, 2}, the magic handles are (x<<2 | 3) * Not necessary for NT 4.0, but it works there, too. * HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK */ HANDLE const rghMagic[3] = {(HANDLE)3, (HANDLE)7, (HANDLE)11}; /* * Close3Handles: helper function for I/O redirection */ void Close3Handles( HANDLE *rgh ) { int iFile; for (iFile=0; iFile<3; ++iFile) { if (rgh[iFile] != 0 && rgh[iFile] != INVALID_HANDLE_VALUE && rgh[iFile] != rghMagic[iFile]) { VERIFY(CloseHandle(rgh[iFile])); rgh[iFile] = 0; } } } XOSD ProcessDebuggeeRedirection( LPTSTR lszCommandLine, STARTUPINFO FAR * psi ) /*++ Routine Description: Parse command line redirection Arguments: szCommandLine A string representing the command line. psi Where we leave our mark Return Value: xosdNone If no error xosdIORedirBadFile If we can't open the file xosdIORedirSyntax If the redir syntax is bad Notes: The redirection-related text is removed from lszCommandLine Redirection is a hack in NT psi -> dwFlags += STARTF+USESTDHANDLES psi -> hStd{Input,Output,Error} are set --*/ { LPTSTR lszArg; LPTSTR lszBegin; LPTSTR lszEnd; BOOL fInQuote = FALSE; BOOL fAppend; // iFile is 0,1,2 for std{in,out,err} int iFile, iFileFrom; CHAR ch; lszArg = lszCommandLine; while (*lszArg) { // skip over quoted text on command line if (*lszArg == '"') { fInQuote = !fInQuote; } if (fInQuote) { // If in quoted text, increment lszArg lszArg = _ftcsinc( lszArg ); continue; } lszBegin = lszArg; // recognize leading digit for "2>blah", "0= '0' && *lszArg <= '2') { // iFile holds the file descriptor iFile = *lszArg - '0'; lszArg = _ftcsinc( lszArg ); if (*lszArg == '\0') { break; } } else { // For 'foo.exe > bar.txt' (no file number), we'll figure it out later. iFile = -1; } // If there is redirection going on, process it. if (*lszArg == '<' || *lszArg == '>') { psi -> dwFlags |= STARTF_USESTDHANDLES; // if there was no explicit leading digit, figure out the // implicit one: 0 for "<", 1 for ">" or ">>" if (iFile == -1) { if (*lszArg == '<') { iFile = 0; } else { iFile = 1; } } else if (iFile == 0) { if (*lszArg == '>') { Close3Handles(rgh); return xosdIORedirSyntax; } } else { if (*lszArg == '<') { Close3Handles(rgh); return xosdIORedirSyntax; } } if (lszArg[0] == '>' && lszArg[1] == '>') { fAppend = TRUE; lszArg = _ftcsinc( lszArg ); } else { fAppend = FALSE; } lszArg = _ftcsinc( lszArg ); // deal with "2>&1" and so on if (*lszArg == '&') { lszArg = _ftcsinc( lszArg ); while (*lszArg == ' ' || *lszArg == '\t') { lszArg = _ftcsinc( lszArg ); } // error conditions: // 1<&x where ix not in [012] // 1<&1 // 2>>&1 if (*lszArg < '0' || *lszArg > '2' || *lszArg - '0' == iFile || fAppend) { Close3Handles(rgh); return xosdIORedirSyntax; } iFileFrom = *lszArg - '0'; if (rgh[iFileFrom] == 0 || rgh[iFileFrom] == INVALID_HANDLE_VALUE) { rgh[iFile] = rgh[iFileFrom]; } else { HANDLE hProcess = GetCurrentProcess(); if (!DuplicateHandle( hProcess, rgh[iFileFrom], hProcess, &rgh[iFile], 0, TRUE, DUPLICATE_SAME_ACCESS)) { Close3Handles(rgh); return xosdIORedirBadFile; } } lszArg = _ftcsinc( lszArg ); // get past last digit } else { static char rgchEndFilename[] = "\t \"&,;<=>"; static SECURITY_ATTRIBUTES sa = {sizeof(sa), 0, TRUE}; // skip blanks after "<" or ">" while (*lszArg == ' ' || *lszArg == '\t') { ++lszArg; } // append null to szArg lszEnd = lszArg; while (*lszEnd && !_tcschr(rgchEndFilename, *lszEnd)) { lszEnd = _ftcsinc( lszEnd ); } ch = *lszEnd; *lszEnd = '\0'; if (iFile) { // std{out,err} rgh[iFile] = CreateFile ( lszArg, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, &sa, fAppend ? OPEN_EXISTING : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); } else { // stdin rgh[iFile] = CreateFile ( lszArg, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); } if (rgh[iFile] == INVALID_HANDLE_VALUE) { // // The file could not be opened as desired. // _ftcsncpy(szErrArg, lszArg, sizeof(szErrArg)); // szErrArg[sizeof(szErrArg)-1] = '\0'; // _ftcsncpy(szErrExe, lszExe, sizeof(szErrExe)); // szErrExe[sizeof(szErrExe)-1] = '\0'; // restore byte after arg *lszEnd = ch; Close3Handles(rgh); return xosdIORedirBadFile; } // restore byte after arg *lszEnd = ch; // if ">>", move to end of file if (fAppend) { SetFilePointer(rgh[iFile], 0, NULL, FILE_END); } // advance lszArg to end of string lszArg = lszEnd; } // remove the redirection from the command line if (*lszArg == ' ' || *lszArg == '\t') { lszArg++; } _fmemmove(lszBegin, lszArg, (_ftcslen(lszArg)+1) * sizeof(TCHAR)); lszArg = lszBegin; } else { lszArg = _ftcsinc( lszArg ); } } // while if (lszCommandLine[0] == ' ' && lszCommandLine[1] == '\0') { lszCommandLine[0] = '\0'; } // If we're redirecting at all if (psi -> dwFlags & STARTF_USESTDHANDLES) { OSVERSIONINFO ver; ver.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); GetVersionEx (&ver); for (iFile=0; iFile<3; ++iFile) { // If they're still unset. if (rgh[iFile] == 0) { // If we are using NT 3.x or greater, // use the hack magic handles. See comments // near the definition of rghMagic // This is a big-time hack. Luckily, as of NT 4.0 it is no longer necessary. if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT && ver.dwMajorVersion == 3) { rgh[iFile] = rghMagic[iFile]; } else { rgh[iFile] = INVALID_HANDLE_VALUE; } } } psi -> hStdInput = rgh[0]; psi -> hStdOutput = rgh[1]; psi -> hStdError = rgh[2]; } return xosdNone; } //ProcessDebuggeeRedirection #ifndef KERNEL void ClearPendingDebugEvents( PID pid, TID tid ) /*++ Routine Description: Removes any pending debug events form the list. The process and thread id pair are used as the search criteria. Arguments: pid - process id tid - thread id Return Value: none --*/ { PDETOSAVE pDeToSave; if (IsListEmpty(&DmListOfPendingDebugEvents)) { return; } pDeToSave = (PDETOSAVE) DmListOfPendingDebugEvents.Flink; while (&pDeToSave->List != &DmListOfPendingDebugEvents) { PDETOSAVE pNext = (PDETOSAVE) pDeToSave->List.Flink; if (pid == pDeToSave->de.dwProcessId && tid == pDeToSave->de.dwThreadId) { RemoveEntryList(&pDeToSave->List); InsertTailList(&DmFreeListOfDebugEvents, &pDeToSave->List); } pDeToSave = pNext; } } PDETOSAVE GetNextFreeDebugEvent( VOID ) /*++ Routine Description: Returns a PDETOSAVE structure from the free list. If the free list is empty a new structure is allocated. Arguments: none Return Value: Pointer to an unused PDETOSAVE structure. --*/ { PDETOSAVE pDeToSave; if (!IsListEmpty(&DmFreeListOfDebugEvents)) { pDeToSave = (PDETOSAVE) RemoveHeadList(&DmFreeListOfDebugEvents); memset(pDeToSave, 0, sizeof(*pDeToSave)); } else { pDeToSave = (PDETOSAVE) MHAlloc(sizeof(DETOSAVE)); if (pDeToSave) { memset(pDeToSave, 0, sizeof(*pDeToSave)); } } return pDeToSave; } PDETOSAVE GetMostRecentDebugEvent(PID pid, TID tid) /*++ Routine Description: Return the most recent DEBUGEVENT for a given process/thread pair. Arguments: pid - process id tid - thread id Return Value: Pointer to the most recent DEBUGEVENT for the given process/thread pair. NULL if the list is empty. --*/ { PDETOSAVE pDeToSave; if (IsListEmpty(&DmListOfPendingDebugEvents)) { return NULL; } pDeToSave = (PDETOSAVE) DmListOfPendingDebugEvents.Blink; while (&pDeToSave->List != &DmListOfPendingDebugEvents) { if (pid == pDeToSave->de.dwProcessId && tid == pDeToSave->de.dwThreadId) { return pDeToSave; } pDeToSave = (PDETOSAVE) pDeToSave->List.Blink; } return NULL; } #endif // !KERNEL