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