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 | // ==++== |
6 | // |
7 | |
8 | // |
9 | // ==--== |
10 | /**************************************************************************** |
11 | * STRIKE.C * |
12 | * Routines for the NTSD extension - STRIKE * |
13 | * * |
14 | * History: * |
15 | * 09/07/99 Microsoft Created * |
16 | * * |
17 | * * |
18 | \***************************************************************************/ |
19 | #include <windows.h> |
20 | #include <winternl.h> |
21 | #include <winver.h> |
22 | #include <wchar.h> |
23 | |
24 | #define NOEXTAPI |
25 | #define KDEXT_64BIT |
26 | #include <wdbgexts.h> |
27 | #undef DECLARE_API |
28 | #undef StackTrace |
29 | |
30 | #include <stdio.h> |
31 | #include <stdlib.h> |
32 | #include <stdint.h> |
33 | #include <string.h> |
34 | #include <stddef.h> |
35 | |
36 | #include "strike.h" |
37 | // We need to define the target address type. This will be used in the |
38 | // functions that read directly from the debuggee address space, vs. using |
39 | // the DAC tgo read the DAC-ized data structures. |
40 | #include "daccess.h" |
41 | //#include "dbgeng.h" |
42 | |
43 | |
44 | #ifndef STRESS_LOG |
45 | #define STRESS_LOG |
46 | #endif // STRESS_LOG |
47 | #define STRESS_LOG_READONLY |
48 | #include "stresslog.h" |
49 | #include <dbghelp.h> |
50 | |
51 | #include "corhdr.h" |
52 | #include "dacprivate.h" |
53 | |
54 | #define CORHANDLE_MASK 0x1 |
55 | #define DEFINE_EXT_GLOBALS |
56 | |
57 | #include "util.h" |
58 | |
59 | #ifndef _ASSERTE |
60 | #ifdef _DEBUG |
61 | #define _ASSERTE(expr) \ |
62 | do { if (!(expr) ) { ExtOut(#expr); DebugBreak(); } } while (0) |
63 | #else // _DEBUG |
64 | #define _ASSERTE(expr) |
65 | #endif // _DEBUG else |
66 | #endif // !_ASSERTE |
67 | |
68 | #ifdef _MSC_VER |
69 | #pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data |
70 | #pragma warning(disable:4189) // local variable is initialized but not referenced |
71 | #endif // _MSC_VER |
72 | |
73 | struct PlugRecord |
74 | { |
75 | PlugRecord *next; |
76 | |
77 | size_t PlugStart; |
78 | size_t PlugEnd; |
79 | size_t Delta; |
80 | |
81 | PlugRecord() { ZeroMemory(this,sizeof(PlugRecord)); } |
82 | }; |
83 | |
84 | struct PromoteRecord |
85 | { |
86 | PromoteRecord *next; |
87 | |
88 | size_t Root; |
89 | size_t Value; |
90 | size_t methodTable; |
91 | |
92 | PromoteRecord() { ZeroMemory(this,sizeof(PromoteRecord)); } |
93 | }; |
94 | |
95 | struct RelocRecord |
96 | { |
97 | RelocRecord *next; |
98 | |
99 | size_t Root; |
100 | size_t PrevValue; |
101 | size_t NewValue; |
102 | size_t methodTable; |
103 | |
104 | RelocRecord() { ZeroMemory(this,sizeof(RelocRecord)); } |
105 | }; |
106 | |
107 | struct GCRecord |
108 | { |
109 | ULONG64 GCCount; |
110 | |
111 | // BOOL IsComplete() { return bFinished && bHaveStart; } |
112 | |
113 | PlugRecord *PlugList; |
114 | RelocRecord *RelocList; |
115 | PromoteRecord *PromoteList; |
116 | |
117 | void AddPlug(PlugRecord& p) { |
118 | PlugRecord *pTmp = PlugList; |
119 | PlugList = new PlugRecord(p); |
120 | PlugList->next = pTmp; |
121 | } |
122 | |
123 | void AddReloc(RelocRecord& r) { |
124 | RelocRecord *pTmp = RelocList; |
125 | RelocList = new RelocRecord(r); |
126 | RelocList->next = pTmp; |
127 | } |
128 | |
129 | void AddPromote(PromoteRecord& r) { |
130 | PromoteRecord *pTmp = PromoteList; |
131 | PromoteList = new PromoteRecord(r); |
132 | PromoteList->next = pTmp; |
133 | } |
134 | |
135 | UINT PlugCount() { |
136 | UINT ret = 0; |
137 | PlugRecord *Iter = PlugList; |
138 | while (Iter) { |
139 | Iter = Iter->next; |
140 | ret++; |
141 | } |
142 | return ret; |
143 | } |
144 | |
145 | UINT RelocCount() { |
146 | UINT ret = 0; |
147 | RelocRecord *Iter = RelocList; |
148 | while (Iter) { |
149 | Iter = Iter->next; |
150 | ret++; |
151 | } |
152 | return ret; |
153 | } |
154 | |
155 | UINT PromoteCount() { |
156 | UINT ret = 0; |
157 | PromoteRecord *Iter = PromoteList; |
158 | while (Iter) { |
159 | Iter = Iter->next; |
160 | ret++; |
161 | } |
162 | return ret; |
163 | } |
164 | |
165 | void Clear() { |
166 | |
167 | PlugRecord *pTrav = PlugList; |
168 | while (pTrav) { |
169 | PlugRecord *pTmp = pTrav->next; |
170 | delete pTrav; |
171 | pTrav = pTmp; |
172 | } |
173 | |
174 | RelocRecord *pTravR = RelocList; |
175 | while (pTravR) { |
176 | RelocRecord *pTmp = pTravR->next; |
177 | delete pTravR; |
178 | pTravR = pTmp; |
179 | } |
180 | |
181 | PromoteRecord *pTravP = PromoteList; |
182 | while (pTravP) { |
183 | PromoteRecord *pTmp = pTravP->next; |
184 | delete pTravP; |
185 | pTravP = pTmp; |
186 | } |
187 | |
188 | ZeroMemory(this,sizeof(GCRecord)); |
189 | } |
190 | |
191 | }; |
192 | |
193 | #define MAX_GCRECORDS 500 |
194 | UINT g_recordCount = 0; |
195 | GCRecord g_records[MAX_GCRECORDS]; |
196 | |
197 | void GcHistClear() |
198 | { |
199 | for (UINT i=0; i < g_recordCount; i++) |
200 | { |
201 | g_records[i].Clear(); |
202 | } |
203 | g_recordCount = 0; |
204 | } |
205 | |
206 | void GcHistAddLog(LPCSTR msg, StressMsg* stressMsg) |
207 | { |
208 | if (g_recordCount >= MAX_GCRECORDS) |
209 | { |
210 | return; |
211 | } |
212 | |
213 | if (strcmp(msg, ThreadStressLog::gcPlugMoveMsg()) == 0) |
214 | { |
215 | PlugRecord pr; |
216 | // this is a plug message |
217 | _ASSERTE(stressMsg->numberOfArgs == 3); |
218 | pr.PlugStart = (size_t) stressMsg->args[0]; |
219 | pr.PlugEnd = (size_t) stressMsg->args[1]; |
220 | pr.Delta = (size_t) stressMsg->args[2]; |
221 | |
222 | g_records[g_recordCount].AddPlug(pr); |
223 | } |
224 | else if (strcmp(msg, ThreadStressLog::gcRootMsg()) == 0) |
225 | { |
226 | // this is a root message |
227 | _ASSERTE(stressMsg->numberOfArgs == 4); |
228 | RelocRecord rr; |
229 | rr.Root = (size_t) stressMsg->args[0]; |
230 | rr.PrevValue = (size_t) stressMsg->args[1]; |
231 | rr.NewValue = (size_t) stressMsg->args[2]; |
232 | rr.methodTable = (size_t) stressMsg->args[3]; |
233 | g_records[g_recordCount].AddReloc(rr); |
234 | } |
235 | else if (strcmp(msg, ThreadStressLog::gcRootPromoteMsg()) == 0) |
236 | { |
237 | // this is a promote message |
238 | _ASSERTE(stressMsg->numberOfArgs == 3); |
239 | PromoteRecord pr; |
240 | pr.Root = (size_t) stressMsg->args[0]; |
241 | pr.Value = (size_t) stressMsg->args[1]; |
242 | pr.methodTable = (size_t) stressMsg->args[2]; |
243 | g_records[g_recordCount].AddPromote(pr); |
244 | } |
245 | else if (strcmp(msg, ThreadStressLog::gcStartMsg()) == 0) |
246 | { |
247 | // Gc start! |
248 | _ASSERTE(stressMsg->numberOfArgs == 3); |
249 | ULONG64 gc_count = (ULONG64) stressMsg->args[0]; |
250 | g_records[g_recordCount].GCCount = gc_count; |
251 | g_recordCount++; |
252 | } |
253 | else if (strcmp(msg, ThreadStressLog::gcEndMsg()) == 0) |
254 | { |
255 | // Gc end! |
256 | // ULONG64 gc_count = (ULONG64) stressMsg->data; |
257 | // ExtOut ("ENDGC %d\n", gc_count); |
258 | } |
259 | } |
260 | |
261 | DECLARE_API(HistStats) |
262 | { |
263 | INIT_API(); |
264 | |
265 | ExtOut ("%8s %8s %8s\n" , |
266 | "GCCount" , "Promotes" , "Relocs" ); |
267 | ExtOut ("-----------------------------------\n" ); |
268 | |
269 | // Just traverse the data structure, printing basic stats |
270 | for (UINT i=0; i < g_recordCount; i++) |
271 | { |
272 | UINT PromoteCount = g_records[i].PromoteCount(); |
273 | UINT RelocCount = g_records[i].RelocCount(); |
274 | UINT GCCount = (UINT) g_records[i].GCCount; |
275 | |
276 | ExtOut ("%8d %8d %8d\n" , |
277 | GCCount, |
278 | PromoteCount, |
279 | RelocCount); |
280 | } |
281 | |
282 | BOOL bErrorFound = FALSE; |
283 | |
284 | // Check for duplicate Reloc or Promote messages within one gc. |
285 | // Method is very inefficient, improve it later. |
286 | for (UINT i=0; i < g_recordCount; i++) |
287 | { |
288 | { // Promotes |
289 | PromoteRecord *Iter = g_records[i].PromoteList; |
290 | UINT GCCount = (UINT) g_records[i].GCCount; |
291 | while (Iter) |
292 | { |
293 | PromoteRecord *innerIter = Iter->next; |
294 | while (innerIter) |
295 | { |
296 | if (Iter->Root == innerIter->Root) |
297 | { |
298 | ExtOut ("Root %p promoted multiple times in gc %d\n" , |
299 | SOS_PTR(Iter->Root), |
300 | GCCount); |
301 | bErrorFound = TRUE; |
302 | } |
303 | innerIter = innerIter->next; |
304 | } |
305 | |
306 | Iter = Iter->next; |
307 | } |
308 | } |
309 | |
310 | { // Relocates |
311 | RelocRecord *Iter = g_records[i].RelocList; |
312 | UINT GCCount = (UINT) g_records[i].GCCount; |
313 | while (Iter) |
314 | { |
315 | RelocRecord *innerIter = Iter->next; |
316 | while (innerIter) |
317 | { |
318 | if (Iter->Root == innerIter->Root) |
319 | { |
320 | ExtOut ("Root %p relocated multiple times in gc %d\n" , |
321 | SOS_PTR(Iter->Root), |
322 | GCCount); |
323 | bErrorFound = TRUE; |
324 | } |
325 | innerIter = innerIter->next; |
326 | } |
327 | |
328 | Iter = Iter->next; |
329 | } |
330 | } |
331 | } |
332 | |
333 | if (!bErrorFound) |
334 | { |
335 | ExtOut ("No duplicate promote or relocate messages found in the log.\n" ); |
336 | } |
337 | |
338 | return Status; |
339 | } |
340 | |
341 | DECLARE_API(HistRoot) |
342 | { |
343 | INIT_API(); |
344 | size_t nArg; |
345 | |
346 | StringHolder rootstr; |
347 | CMDValue arg[] = |
348 | { |
349 | // vptr, type |
350 | {&rootstr.data, COSTRING}, |
351 | }; |
352 | |
353 | if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) |
354 | return Status; |
355 | |
356 | if (nArg != 1) |
357 | { |
358 | ExtOut ("!Root <valid object pointer>\n" ); |
359 | return Status; |
360 | } |
361 | |
362 | size_t Root = (size_t) GetExpression(rootstr.data); |
363 | |
364 | ExtOut ("%8s %" POINTERSIZE "s %" POINTERSIZE "s %9s %20s\n" , |
365 | "GCCount" , "Value" , "MT" , "Promoted?" , "Notes" ); |
366 | ExtOut ("---------------------------------------------------------\n" ); |
367 | |
368 | bool bBoringPeople = false; |
369 | |
370 | // Just traverse the data structure, printing basic stats |
371 | for (UINT i=0; i < g_recordCount; i++) |
372 | { |
373 | UINT GCCount = (UINT) g_records[i].GCCount; |
374 | |
375 | // Find promotion records...there should only be one. |
376 | PromoteRecord *pPtr = g_records[i].PromoteList; |
377 | PromoteRecord *pPromoteRec = NULL; |
378 | bool bPromotedMoreThanOnce = false; |
379 | while(pPtr) |
380 | { |
381 | if (pPtr->Root == Root) |
382 | { |
383 | if (pPromoteRec) |
384 | { |
385 | bPromotedMoreThanOnce = true; |
386 | } |
387 | else |
388 | { |
389 | pPromoteRec = pPtr; |
390 | } |
391 | } |
392 | pPtr = pPtr->next; |
393 | } |
394 | |
395 | RelocRecord *pReloc = g_records[i].RelocList; |
396 | RelocRecord *pRelocRec = NULL; |
397 | bool bRelocatedMoreThanOnce = false; |
398 | while(pReloc) |
399 | { |
400 | if (pReloc->Root == Root) |
401 | { |
402 | if (pRelocRec) |
403 | { |
404 | bRelocatedMoreThanOnce = true; |
405 | } |
406 | else |
407 | { |
408 | pRelocRec = pReloc; |
409 | } |
410 | } |
411 | pReloc = pReloc->next; |
412 | } |
413 | |
414 | // Validate the records found for this root. |
415 | if (pRelocRec != NULL) |
416 | { |
417 | bBoringPeople = false; |
418 | |
419 | ExtOut ("%8d %p %p %9s " , GCCount, |
420 | SOS_PTR(pRelocRec->NewValue), |
421 | SOS_PTR(pRelocRec->methodTable), |
422 | pPromoteRec ? "yes" : "no" ); |
423 | if (pPromoteRec != NULL) |
424 | { |
425 | // There should be similarities between the promote and reloc record |
426 | if (pPromoteRec->Value != pRelocRec->PrevValue || |
427 | pPromoteRec->methodTable != pRelocRec->methodTable) |
428 | { |
429 | ExtOut ("promote/reloc records in error " ); |
430 | } |
431 | |
432 | if (bPromotedMoreThanOnce || bRelocatedMoreThanOnce) |
433 | { |
434 | ExtOut ("Duplicate promote/relocs" ); |
435 | } |
436 | } |
437 | ExtOut ("\n" ); |
438 | } |
439 | else if (pPromoteRec) |
440 | { |
441 | ExtOut ("Error: There is a promote record for root %p, but no relocation record\n" , |
442 | (ULONG64) pPromoteRec->Root); |
443 | } |
444 | else |
445 | { |
446 | if (!bBoringPeople) |
447 | { |
448 | ExtOut ("...\n" ); |
449 | bBoringPeople = true; |
450 | } |
451 | } |
452 | } |
453 | return Status; |
454 | } |
455 | |
456 | DECLARE_API(HistObjFind) |
457 | { |
458 | INIT_API(); |
459 | size_t nArg; |
460 | |
461 | StringHolder objstr; |
462 | CMDValue arg[] = |
463 | { |
464 | // vptr, type |
465 | {&objstr.data, COSTRING}, |
466 | }; |
467 | |
468 | if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) |
469 | return Status; |
470 | |
471 | if (nArg != 1) |
472 | { |
473 | ExtOut ("!ObjSearch <valid object pointer>\n" ); |
474 | return Status; |
475 | } |
476 | |
477 | size_t object = (size_t) GetExpression(objstr.data); |
478 | |
479 | ExtOut ("%8s %" POINTERSIZE "s %40s\n" , |
480 | "GCCount" , "Object" , "Message" ); |
481 | ExtOut ("---------------------------------------------------------\n" ); |
482 | |
483 | size_t curAddress = object; |
484 | bool bBoringPeople = false; |
485 | |
486 | // Just traverse the data structure, printing basic stats |
487 | for (UINT i=0; i < g_recordCount; i++) |
488 | { |
489 | if (curAddress == 0) |
490 | { |
491 | break; |
492 | } |
493 | |
494 | UINT GCCount = (UINT) g_records[i].GCCount; |
495 | |
496 | PromoteRecord *pPtr = g_records[i].PromoteList; |
497 | while(pPtr) |
498 | { |
499 | if (pPtr->Value == curAddress) |
500 | { |
501 | bBoringPeople = false; |
502 | ExtOut ("%8d %p " , GCCount, SOS_PTR(curAddress)); |
503 | ExtOut ("Promotion for root %p (MT = %p)\n" , |
504 | SOS_PTR(pPtr->Root), |
505 | SOS_PTR(pPtr->methodTable)); |
506 | } |
507 | pPtr = pPtr->next; |
508 | } |
509 | |
510 | RelocRecord *pReloc = g_records[i].RelocList; |
511 | while(pReloc) |
512 | { |
513 | if (pReloc->NewValue == curAddress || |
514 | pReloc->PrevValue == curAddress) |
515 | { |
516 | bBoringPeople = false; |
517 | ExtOut ("%8d %p " , GCCount, SOS_PTR(curAddress)); |
518 | ExtOut ("Relocation %s for root %p\n" , |
519 | (pReloc->NewValue == curAddress) ? "NEWVALUE" : "PREVVALUE" , |
520 | SOS_PTR(pReloc->Root)); |
521 | } |
522 | pReloc = pReloc->next; |
523 | } |
524 | |
525 | if (!bBoringPeople) |
526 | { |
527 | ExtOut ("...\n" ); |
528 | bBoringPeople = true; |
529 | } |
530 | |
531 | } |
532 | return Status; |
533 | } |
534 | |
535 | DECLARE_API(HistObj) |
536 | { |
537 | INIT_API(); |
538 | size_t nArg; |
539 | |
540 | StringHolder objstr; |
541 | CMDValue arg[] = |
542 | { |
543 | // vptr, type |
544 | {&objstr.data, COSTRING}, |
545 | }; |
546 | |
547 | if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) |
548 | return Status; |
549 | |
550 | if (nArg != 1) |
551 | { |
552 | ExtOut ("!object <valid object pointer>\n" ); |
553 | return Status; |
554 | } |
555 | |
556 | size_t object = (size_t) GetExpression(objstr.data); |
557 | |
558 | ExtOut ("%8s %" POINTERSIZE "s %40s\n" , |
559 | "GCCount" , "Object" , "Roots" ); |
560 | ExtOut ("---------------------------------------------------------\n" ); |
561 | |
562 | size_t curAddress = object; |
563 | |
564 | // Just traverse the data structure, printing basic stats |
565 | for (UINT i=0; i < g_recordCount; i++) |
566 | { |
567 | if (curAddress == 0) |
568 | { |
569 | break; |
570 | } |
571 | |
572 | UINT GCCount = (UINT) g_records[i].GCCount; |
573 | |
574 | ExtOut ("%8d %p " , GCCount, SOS_PTR(curAddress)); |
575 | |
576 | RelocRecord *pReloc = g_records[i].RelocList; |
577 | size_t candidateCurAddress = curAddress; |
578 | bool bFirstReloc = true; |
579 | while(pReloc) |
580 | { |
581 | if (pReloc->NewValue == curAddress) |
582 | { |
583 | ExtOut ("%p, " , SOS_PTR(pReloc->Root)); |
584 | if (bFirstReloc) |
585 | { |
586 | candidateCurAddress = pReloc->PrevValue; |
587 | bFirstReloc = false; |
588 | } |
589 | else if (candidateCurAddress != pReloc->PrevValue) |
590 | { |
591 | ExtOut ("differing reloc values for this object!\n" ); |
592 | } |
593 | } |
594 | pReloc = pReloc->next; |
595 | } |
596 | |
597 | ExtOut ("\n" ); |
598 | curAddress = candidateCurAddress; |
599 | } |
600 | return Status; |
601 | } |
602 | |
603 | DECLARE_API(HistInit) |
604 | { |
605 | INIT_API(); |
606 | |
607 | GcHistClear(); |
608 | |
609 | CLRDATA_ADDRESS stressLogAddr = 0; |
610 | if (g_sos->GetStressLogAddress(&stressLogAddr) != S_OK) |
611 | { |
612 | ExtOut("Unable to find stress log via DAC\n" ); |
613 | return E_FAIL; |
614 | } |
615 | |
616 | ExtOut ("Attempting to read Stress log\n" ); |
617 | |
618 | Status = StressLog::Dump(stressLogAddr, NULL, g_ExtData); |
619 | if (Status == S_OK) |
620 | ExtOut("SUCCESS: GCHist structures initialized\n" ); |
621 | else if (Status == S_FALSE) |
622 | ExtOut("No Stress log in the image, GCHist commands unavailable\n" ); |
623 | else |
624 | ExtOut("FAILURE: Stress log unreadable\n" ); |
625 | |
626 | return Status; |
627 | } |
628 | |
629 | DECLARE_API(HistClear) |
630 | { |
631 | INIT_API(); |
632 | GcHistClear(); |
633 | ExtOut("Completed successfully.\n" ); |
634 | return Status; |
635 | } |
636 | |
637 | |