| 1 | // Licensed to the .NET Foundation under one or more agreements. |
| 2 | // The .NET Foundation licenses this file to you under the MIT license. |
| 3 | // See the LICENSE file in the project root for more information. |
| 4 | |
| 5 | // Type-safe helper wrapper to get an EXCEPTION_RECORD slot as a CORDB_ADDRESS |
| 6 | // |
| 7 | // Arguments: |
| 8 | // pRecord - exception record |
| 9 | // idxSlot - slot to retrieve from. |
| 10 | // |
| 11 | // Returns: |
| 12 | // contents of slot as a CordbAddress. |
| 13 | CORDB_ADDRESS GetExceptionInfoAsAddress(const EXCEPTION_RECORD * pRecord, int idxSlot) |
| 14 | { |
| 15 | _ASSERTE((idxSlot >= 0) && (idxSlot < EXCEPTION_MAXIMUM_PARAMETERS)); |
| 16 | |
| 17 | // ExceptionInformation is an array of ULONG_PTR. CORDB_ADDRESS is a 0-extended ULONG64. |
| 18 | // So the implicit cast will work here on x86. On 64-bit, it's basically a nop. |
| 19 | return pRecord->ExceptionInformation[idxSlot]; |
| 20 | } |
| 21 | |
| 22 | |
| 23 | // Determine if an exception event is a Debug event for this flavor of the CLR. |
| 24 | // |
| 25 | // Arguments: |
| 26 | // pRecord - exception record |
| 27 | // pClrBaseAddress - clr Instance ID for which CLR in the target we're checking against. |
| 28 | // |
| 29 | // Returns: |
| 30 | // NULL if the exception is not a CLR managed debug event for the given Clr instance. |
| 31 | // Else, address in target process of managed debug event described by the exception (the payload). |
| 32 | // |
| 33 | // Notes: |
| 34 | // This decodes events raised by code:Debugger.SendRawEvent |
| 35 | // Anybody can spoof our exception, so this is not a reliably safe method. |
| 36 | // With multiple CLRs in the same process, it's essential to use the proper pClrBaseAddress. |
| 37 | CORDB_ADDRESS IsEventDebuggerNotification( |
| 38 | const EXCEPTION_RECORD * pRecord, |
| 39 | CORDB_ADDRESS pClrBaseAddress |
| 40 | ) |
| 41 | { |
| 42 | _ASSERTE(pRecord != NULL); |
| 43 | |
| 44 | // Must specify a CLR instance. |
| 45 | _ASSERTE(pClrBaseAddress != NULL); |
| 46 | |
| 47 | // If it's not even our exception code, then it's not ours. |
| 48 | if (pRecord->ExceptionCode != CLRDBG_NOTIFICATION_EXCEPTION_CODE) |
| 49 | { |
| 50 | return NULL; |
| 51 | } |
| 52 | |
| 53 | // |
| 54 | // Format of an ExceptionInformation parameter is: |
| 55 | // 0: cookie (CLRDBG_EXCEPTION_DATA_CHECKSUM) |
| 56 | // 1: Base address of mscorwks. This identifies the instance of the CLR. |
| 57 | // 2: Target Address of DebuggerIPCEvent, which contains the "real" event. |
| 58 | // |
| 59 | if (pRecord->NumberParameters != 3) |
| 60 | { |
| 61 | return NULL; |
| 62 | } |
| 63 | |
| 64 | // 1st argument should always be the cookie. |
| 65 | // If cookie doesn't match, very likely it's a stray exception that happens to be using |
| 66 | // our code. |
| 67 | DWORD cookie = (DWORD) pRecord->ExceptionInformation[0]; |
| 68 | if (cookie != CLRDBG_EXCEPTION_DATA_CHECKSUM) |
| 69 | { |
| 70 | return NULL; |
| 71 | } |
| 72 | |
| 73 | // TODO: We don't do this check in case of non-windows debugging now, because we don't support |
| 74 | // multi-instance debugging. |
| 75 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) && !defined(FEATURE_DBGIPC_TRANSPORT_DI) |
| 76 | // If base-address doesn't match, then it's likely an event from another version of the CLR |
| 77 | // in the target. |
| 78 | // We need to be careful here. CORDB_ADDRESS is a ULONG64, whereas ExceptionInformation[1] |
| 79 | // is ULONG_PTR. So on 32-bit, their sizes don't match. |
| 80 | CORDB_ADDRESS pTargetBase = GetExceptionInfoAsAddress(pRecord, 1); |
| 81 | if (pTargetBase != pClrBaseAddress) |
| 82 | { |
| 83 | return NULL; |
| 84 | } |
| 85 | #endif |
| 86 | |
| 87 | // It passes all the format checks. So now get the payload. |
| 88 | CORDB_ADDRESS ptrRemoteManagedEvent = GetExceptionInfoAsAddress(pRecord, 2); |
| 89 | |
| 90 | return ptrRemoteManagedEvent; |
| 91 | } |
| 92 | |
| 93 | #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) |
| 94 | void InitEventForDebuggerNotification(DEBUG_EVENT * pDebugEvent, |
| 95 | CORDB_ADDRESS pClrBaseAddress, |
| 96 | DebuggerIPCEvent * pIPCEvent) |
| 97 | { |
| 98 | pDebugEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT; |
| 99 | |
| 100 | pDebugEvent->u.Exception.dwFirstChance = TRUE; |
| 101 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionCode = CLRDBG_NOTIFICATION_EXCEPTION_CODE; |
| 102 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionFlags = 0; |
| 103 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionRecord = NULL; |
| 104 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionAddress = 0; |
| 105 | |
| 106 | // |
| 107 | // Format of an ExceptionInformation parameter is: |
| 108 | // 0: cookie (CLRDBG_EXCEPTION_DATA_CHECKSUM) |
| 109 | // 1: Base address of mscorwks. This identifies the instance of the CLR. |
| 110 | // 2: Target Address of DebuggerIPCEvent, which contains the "real" event. |
| 111 | // |
| 112 | pDebugEvent->u.Exception.ExceptionRecord.NumberParameters = 3; |
| 113 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] = CLRDBG_EXCEPTION_DATA_CHECKSUM; |
| 114 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[1] = (ULONG_PTR)CORDB_ADDRESS_TO_PTR(pClrBaseAddress); |
| 115 | pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[2] = (ULONG_PTR)pIPCEvent; |
| 116 | |
| 117 | _ASSERTE(IsEventDebuggerNotification(&(pDebugEvent->u.Exception.ExceptionRecord), pClrBaseAddress) == |
| 118 | PTR_TO_CORDB_ADDRESS(pIPCEvent)); |
| 119 | } |
| 120 | #endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) |
| 121 | |
| 122 | //----------------------------------------------------------------------------- |
| 123 | // Helper to get the proper decorated name |
| 124 | // Caller ensures that pBufSize is large enough. We'll assert just to check, |
| 125 | // but no runtime failure. |
| 126 | // pBuf - the output buffer to write the decorated name in |
| 127 | // cBufSizeInChars - the size of the buffer in characters, including the null. |
| 128 | // pPrefx - The undecorated name of the event. |
| 129 | //----------------------------------------------------------------------------- |
| 130 | void GetPidDecoratedName(__out_z __out_ecount(cBufSizeInChars) WCHAR * pBuf, int cBufSizeInChars, const WCHAR * pPrefix, DWORD pid) |
| 131 | { |
| 132 | const WCHAR szGlobal[] = W("Global\\" ); |
| 133 | int szGlobalLen; |
| 134 | szGlobalLen = NumItems(szGlobal) - 1; |
| 135 | |
| 136 | // Caller should always give us a big enough buffer. |
| 137 | _ASSERTE(cBufSizeInChars > (int) wcslen(pPrefix) + szGlobalLen); |
| 138 | |
| 139 | // PERF: We are no longer calling GetSystemMetrics in an effort to prevent |
| 140 | // superfluous DLL loading on startup. Instead, we're prepending |
| 141 | // "Global\" to named kernel objects if we are on NT5 or above. The |
| 142 | // only bad thing that results from this is that you can't debug |
| 143 | // cross-session on NT4. Big bloody deal. |
| 144 | wcscpy_s(pBuf, cBufSizeInChars, szGlobal); |
| 145 | pBuf += szGlobalLen; |
| 146 | cBufSizeInChars -= szGlobalLen; |
| 147 | |
| 148 | int ret; |
| 149 | ret = _snwprintf_s(pBuf, cBufSizeInChars, _TRUNCATE, pPrefix, pid); |
| 150 | |
| 151 | // Since this is all determined at compile time, we know we should have enough buffer. |
| 152 | _ASSERTE (ret != STRUNCATE); |
| 153 | } |
| 154 | |
| 155 | //----------------------------------------------------------------------------- |
| 156 | // The 'internal' version of our IL to Native map (the DebuggerILToNativeMap struct) |
| 157 | // has an extra field - ICorDebugInfo::SourceTypes source. The 'external/user-visible' |
| 158 | // version (COR_DEBUG_IL_TO_NATIVE_MAP) lacks that field, so we need to translate our |
| 159 | // internal version to the external version. |
| 160 | // "Export" seemed more succinct than "CopyInternalToExternalILToNativeMap" :) |
| 161 | //----------------------------------------------------------------------------- |
| 162 | void ExportILToNativeMap(ULONG32 cMap, // [in] Min size of mapExt, mapInt |
| 163 | COR_DEBUG_IL_TO_NATIVE_MAP mapExt[], // [in] Filled in here |
| 164 | struct DebuggerILToNativeMap mapInt[],// [in] Source of info |
| 165 | SIZE_T sizeOfCode) // [in] Total size of method (bytes) |
| 166 | { |
| 167 | ULONG32 iMap; |
| 168 | _ASSERTE(mapExt != NULL); |
| 169 | _ASSERTE(mapInt != NULL); |
| 170 | |
| 171 | for(iMap=0; iMap < cMap; iMap++) |
| 172 | { |
| 173 | mapExt[iMap].ilOffset = mapInt[iMap].ilOffset ; |
| 174 | mapExt[iMap].nativeStartOffset = mapInt[iMap].nativeStartOffset ; |
| 175 | mapExt[iMap].nativeEndOffset = mapInt[iMap].nativeEndOffset ; |
| 176 | |
| 177 | // An element that has an end offset of zero, means "till the end of |
| 178 | // the method". Pretty this up so that customers don't have to care about |
| 179 | // this. |
| 180 | if ((DWORD)mapInt[iMap].source & (DWORD)ICorDebugInfo::NATIVE_END_OFFSET_UNKNOWN) |
| 181 | { |
| 182 | mapExt[iMap].nativeEndOffset = (ULONG32)sizeOfCode; |
| 183 | } |
| 184 | |
| 185 | #if defined(_DEBUG) |
| 186 | { |
| 187 | // UnsafeGetConfigDWORD |
| 188 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 189 | static int fReturnSourceTypeForTesting = -1; |
| 190 | if (fReturnSourceTypeForTesting == -1) |
| 191 | fReturnSourceTypeForTesting = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ReturnSourceTypeForTesting); |
| 192 | |
| 193 | if (fReturnSourceTypeForTesting) |
| 194 | { |
| 195 | // Steal the most significant four bits from the native end offset for the source type. |
| 196 | _ASSERTE( (mapExt[iMap].nativeEndOffset >> 28) == 0x0 ); |
| 197 | _ASSERTE( (ULONG32)(mapInt[iMap].source) < 0xF ); |
| 198 | mapExt[iMap].nativeEndOffset |= ((ULONG32)(mapInt[iMap].source) << 28); |
| 199 | } |
| 200 | } |
| 201 | #endif // _DEBUG |
| 202 | } |
| 203 | } |
| 204 | |