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  *                                  GCDump.cpp
6  *
7  * Defines functions to display the GCInfo as defined by the GC-encoding
8  * spec. The GC information may be either dynamically created by a
9  * Just-In-Time compiler conforming to the standard code-manager spec,
10  * or may be persisted by a managed native code compiler conforming
11  * to the standard code-manager spec.
12  */
13 #include "common.h"
14 
15 #if (defined(_DEBUG) || defined(DACCESS_COMPILE))
16 
17 #include "gcenv.h"
18 #include "varint.h"
19 #include "gcinfo.h"
20 #include "gcdump.h"
21 
22 /*****************************************************************************/
23 
24 #ifdef DACCESS_COMPILE
DacNullPrintf(const char *,...)25 static void DacNullPrintf(const char* , ...) {}
26 #endif
27 
GCDump()28 GCDump::GCDump()
29 {
30 #ifndef DACCESS_COMPILE
31     // By default, use the standard printf function to dump
32     GCDump::gcPrintf = (printfFtn) ::printf;
33 #else
34     // Default for DAC is a no-op.
35     GCDump::gcPrintf = DacNullPrintf;
36 #endif
37 }
38 
39 
40 
41 /*****************************************************************************/
42 
43 static const char * const calleeSaveRegMaskBitNumberToName[] =
44 {
45 #ifdef _ARM_
46     "R4",
47     "R5",
48     "R6",
49     "R7",
50     "R8",
51     "R9",
52     "R10",
53     "R11",
54     "LR",
55 #else // _ARM_
56     "EBX",
57     "ESI",
58     "EDI",
59     "EBP",
60     "R12",
61     "R13",
62     "R14",
63     "R15"
64 #endif // _ARM_
65 };
66 
DumpInfoHeader(PTR_UInt8 gcInfo,Tables * pTables,GCInfoHeader * pHeader)67 size_t FASTCALL   GCDump::DumpInfoHeader (PTR_UInt8      gcInfo,
68                                           Tables *       pTables,
69                                           GCInfoHeader * pHeader         /* OUT */
70                                           )
71 {
72     size_t    headerSize = 0;
73     PTR_UInt8 gcInfoStart = gcInfo;
74     PTR_UInt8 pbStackChanges = 0;
75     PTR_UInt8 pbUnwindInfo = 0;
76 
77     unsigned unwindInfoBlobOffset = VarInt::ReadUnsigned(gcInfo);
78     bool    inlineUnwindInfo = (unwindInfoBlobOffset == 0);
79 
80     if (inlineUnwindInfo)
81     {
82         // it is inline..
83         pbUnwindInfo = gcInfo;
84     }
85     else
86     {
87         // The offset was adjusted by 1 to reserve the 0 encoding for the inline case, so we re-adjust it to
88         // the actual offset here.
89         pbUnwindInfo = pTables->pbUnwindInfoBlob + unwindInfoBlobOffset - 1;
90     }
91 
92     // @TODO: decode all funclet headers as well.
93     pbStackChanges = pHeader->DecodeHeader(0, pbUnwindInfo, &headerSize );
94 
95     if (inlineUnwindInfo)
96         gcInfo += headerSize;
97 
98     unsigned epilogCount = pHeader->GetEpilogCount();
99     bool     epilogAtEnd = pHeader->IsEpilogAtEnd();
100 
101     gcPrintf("   prologSize:     %d\n", pHeader->GetPrologSize());
102     if (pHeader->HasVaryingEpilogSizes())
103         gcPrintf("   epilogSize:     (varies)\n");
104     else
105         gcPrintf("   epilogSize:     %d\n", pHeader->GetFixedEpilogSize());
106 
107     gcPrintf("   epilogCount:    %d %s\n", epilogCount, epilogAtEnd ? "[end]" : "");
108     const char * returnKind = "????";
109     unsigned reversePinvokeFrameOffset = 0;     // it can't be 0 because [ebp+0] is the previous ebp
110     switch (pHeader->GetReturnKind())
111     {
112         case GCInfoHeader::MRK_ReturnsScalar:   returnKind = "scalar";    break;
113         case GCInfoHeader::MRK_ReturnsObject:   returnKind = "object";    break;
114         case GCInfoHeader::MRK_ReturnsByref:    returnKind = "byref";     break;
115         case GCInfoHeader::MRK_ReturnsToNative:
116             returnKind = "to native";
117             reversePinvokeFrameOffset = pHeader->GetReversePinvokeFrameOffset();
118             break;
119         case GCInfoHeader::MRK_Unknown:
120             //ASSERT("Unexpected return kind")
121             break;
122     }
123     gcPrintf("   returnKind:     %s\n", returnKind);
124     gcPrintf("   frameKind:      %s", pHeader->HasFramePointer() ? "EBP" : "ESP");
125 #ifdef _TARGET_AMD64_
126     if (pHeader->HasFramePointer())
127         gcPrintf(" offset: %d", pHeader->GetFramePointerOffset());
128 #endif // _AMD64_
129     gcPrintf("\n");
130     gcPrintf("   frameSize:      %d\n", pHeader->GetFrameSize());
131 
132     if (pHeader->HasDynamicAlignment()) {
133         gcPrintf("   alignment:      %d\n", (1 << pHeader->GetDynamicAlignment()));
134         if (pHeader->GetParamPointerReg() != RN_NONE) {
135             gcPrintf("   paramReg:       %d\n", pHeader->GetParamPointerReg());
136         }
137     }
138 
139     gcPrintf("   savedRegs:      ");
140     CalleeSavedRegMask savedRegs = pHeader->GetSavedRegs();
141     CalleeSavedRegMask mask = (CalleeSavedRegMask) 1;
142     for (int i = 0; i < RBM_CALLEE_SAVED_REG_COUNT; i++)
143     {
144         if (savedRegs & mask)
145         {
146             gcPrintf("%s ", calleeSaveRegMaskBitNumberToName[i]);
147         }
148         mask = (CalleeSavedRegMask)(mask << 1);
149     }
150     gcPrintf("\n");
151 
152 #ifdef _TARGET_ARM_
153     gcPrintf("   parmRegsPushedCount: %d\n", pHeader->ParmRegsPushedCount());
154 #endif
155 
156 #ifdef _TARGET_X86_
157     gcPrintf("   returnPopSize:  %d\n", pHeader->GetReturnPopSize());
158     if (pHeader->HasStackChanges())
159     {
160         // @TODO: need to read the stack changes string that follows
161         ASSERT(!"NYI -- stack changes for ESP frames");
162     }
163 #endif
164 
165     if (reversePinvokeFrameOffset != 0)
166     {
167         gcPrintf("   reversePinvokeFrameOffset: 0x%02x\n", reversePinvokeFrameOffset);
168     }
169 
170 
171     if (!epilogAtEnd || (epilogCount > 2))
172     {
173         gcPrintf("   epilog offsets: ");
174         unsigned previousOffset = 0;
175         for (unsigned idx = 0; idx < epilogCount; idx++)
176         {
177             unsigned newOffset = previousOffset + VarInt::ReadUnsigned(gcInfo);
178             gcPrintf("0x%04x ", newOffset);
179             if (pHeader->HasVaryingEpilogSizes())
180                 gcPrintf("(%u bytes) ", VarInt::ReadUnsigned(gcInfo));
181             previousOffset = newOffset;
182         }
183         gcPrintf("\n");
184     }
185 
186     return gcInfo - gcInfoStart;
187 }
188 
PrintLocalSlot(UInt32 slotNum,GCInfoHeader const * pHeader)189 void GCDump::PrintLocalSlot(UInt32 slotNum, GCInfoHeader const * pHeader)
190 {
191     // @TODO: print both EBP/ESP offsets where appropriate
192 #ifdef _TARGET_ARM_
193     gcPrintf("local slot 0n%d, [R7+%02X] \n", slotNum,
194                 ((GCInfoHeader*)pHeader)->GetFrameSize() - ((slotNum + 1) * POINTER_SIZE));
195 #else
196     const char* regAndSign = "EBP-";
197     size_t offset = pHeader->GetPreservedRegsSaveSize() + (slotNum * POINTER_SIZE);
198 # ifdef _TARGET_AMD64_
199     if (((GCInfoHeader*)pHeader)->GetFramePointerOffset() == 0)
200     {
201         regAndSign = "RBP-";
202     }
203     else
204     {
205         regAndSign = "RBP+";
206         offset = (slotNum * POINTER_SIZE);
207     }
208 # endif
209     gcPrintf("local slot 0n%d, [%s%02X] \n", slotNum, regAndSign, offset);
210 #endif
211 }
212 
DumpCallsiteString(UInt32 callsiteOffset,PTR_UInt8 pbCallsiteString,GCInfoHeader const * pHeader)213 void GCDump::DumpCallsiteString(UInt32 callsiteOffset, PTR_UInt8 pbCallsiteString,
214                                 GCInfoHeader const * pHeader)
215 {
216     gcPrintf("%04x: ", callsiteOffset);
217 
218     int count = 0;
219     UInt8 b;
220     PTR_UInt8 pCursor = pbCallsiteString;
221 
222     bool last = false;
223     bool first = true;
224 
225     do
226     {
227         if (!first)
228             gcPrintf("      ");
229 
230         first = false;
231 
232         b = *pCursor++;
233         last = ((b & 0x20) == 0x20);
234 
235         switch (b & 0xC0)
236         {
237         case 0x00:
238             {
239                 // case 2 -- "register set"
240                 gcPrintf("%02x          | 2  ", b);
241 #ifdef _TARGET_ARM_
242                 if (b & CSR_MASK_R4) { gcPrintf("R4 "); count++; }
243                 if (b & CSR_MASK_R5) { gcPrintf("R5 "); count++; }
244                 if (b & CSR_MASK_R6) { gcPrintf("R6 "); count++; }
245                 if (b & CSR_MASK_R7) { gcPrintf("R7 "); count++; }
246                 if (b & CSR_MASK_R8) { gcPrintf("R8 "); count++; }
247 #elif defined(_TARGET_ARM64_)
248                 // ARM64TODO: not all of these are needed?
249                 if (b & CSR_MASK_X19) { gcPrintf("X19 "); count++; }
250                 if (b & CSR_MASK_X20) { gcPrintf("X20 "); count++; }
251                 if (b & CSR_MASK_X21) { gcPrintf("X21 "); count++; }
252                 if (b & CSR_MASK_X22) { gcPrintf("X22 "); count++; }
253                 if (b & CSR_MASK_X23) { gcPrintf("X23 "); count++; }
254                 if (b & CSR_MASK_X24) { gcPrintf("X24 "); count++; }
255                 if (b & CSR_MASK_X25) { gcPrintf("X25 "); count++; }
256                 if (b & CSR_MASK_X26) { gcPrintf("X26 "); count++; }
257                 if (b & CSR_MASK_X27) { gcPrintf("X27 "); count++; }
258                 if (b & CSR_MASK_X28) { gcPrintf("X28 "); count++; }
259 #else // _ARM_
260                 if (b & CSR_MASK_RBX) { gcPrintf("RBX "); count++; }
261                 if (b & CSR_MASK_RSI) { gcPrintf("RSI "); count++; }
262                 if (b & CSR_MASK_RDI) { gcPrintf("RDI "); count++; }
263                 if (b & CSR_MASK_RBP) { gcPrintf("RBP "); count++; }
264                 if (b & CSR_MASK_R12) { gcPrintf("R12 "); count++; }
265 #endif // _ARM_
266                 gcPrintf("\n");
267             }
268             break;
269 
270         case 0x40:
271             {
272                 // case 3 -- "register"
273                 const char* regName = "???";
274                 const char* interior = (b & 0x10) ? "+" : "";
275                 const char* pinned   = (b & 0x08) ? "!" : "";
276 
277                 switch (b & 0x7)
278                 {
279 #ifdef _TARGET_ARM_
280                 case CSR_NUM_R4: regName = "R4"; break;
281                 case CSR_NUM_R5: regName = "R5"; break;
282                 case CSR_NUM_R6: regName = "R6"; break;
283                 case CSR_NUM_R7: regName = "R7"; break;
284                 case CSR_NUM_R8: regName = "R8"; break;
285                 case CSR_NUM_R9: regName = "R9"; break;
286                 case CSR_NUM_R10: regName = "R10"; break;
287                 case CSR_NUM_R11: regName = "R11"; break;
288 #elif defined(_TARGET_ARM64_)
289                 case CSR_NUM_X19: regName = "X19"; break;
290                 case CSR_NUM_X20: regName = "X20"; break;
291                 case CSR_NUM_X21: regName = "X21"; break;
292                 case CSR_NUM_X22: regName = "X22"; break;
293                 case CSR_NUM_X23: regName = "X23"; break;
294                 case CSR_NUM_X24: regName = "X24"; break;
295                 case CSR_NUM_X25: regName = "X25"; break;
296                 case CSR_NUM_X26: regName = "X26"; break;
297                 case CSR_NUM_X27: regName = "X27"; break;
298                 case CSR_NUM_X28: regName = "X28"; break;
299 #else // _ARM_
300                 case CSR_NUM_RBX: regName = "RBX"; break;
301                 case CSR_NUM_RSI: regName = "RSI"; break;
302                 case CSR_NUM_RDI: regName = "RDI"; break;
303                 case CSR_NUM_RBP: regName = "RBP"; break;
304 #ifdef _TARGET_AMD64_
305                 case CSR_NUM_R12: regName = "R12"; break;
306                 case CSR_NUM_R13: regName = "R13"; break;
307                 case CSR_NUM_R14: regName = "R14"; break;
308                 case CSR_NUM_R15: regName = "R15"; break;
309 #endif // _TARGET_AMD64_
310 #endif // _ARM_
311                 }
312                 gcPrintf("%02x          | 3  %s%s%s \n", b, regName, interior, pinned);
313                 count++;
314             }
315             break;
316 
317         case 0x80:
318             {
319                 if (b & 0x10)
320                 {
321                     // case 4 -- "local slot set"
322                     gcPrintf("%02x          | 4  ", b);
323                     bool isFirst = true;
324 
325                     int mask = 0x01;
326                     int slotNum = 0;
327                     while (mask <= 0x08)
328                     {
329                         if (b & mask)
330                         {
331                             if (!isFirst)
332                             {
333                                 if (!first)
334                                     gcPrintf("      ");
335                                 gcPrintf("            |    ");
336                             }
337 
338                             PrintLocalSlot(slotNum, pHeader);
339 
340                             isFirst = false;
341                             count++;
342                         }
343                         mask <<= 1;
344                         slotNum++;
345                     }
346                 }
347                 else
348                 {
349                     // case 5 -- "local slot"
350                     int slotNum = (int)(b & 0xF) + 4;
351                     gcPrintf("%02x          | 5  ", b);
352                     PrintLocalSlot(slotNum, pHeader);
353 
354                     count++;
355                 }
356             }
357             break;
358         case 0xC0:
359             {
360                 gcPrintf("%02x ", b);
361                 unsigned mask = 0;
362                 PTR_UInt8 pInts = pCursor;
363                 unsigned offset = VarInt::ReadUnsigned(pCursor);
364                 const char* interior = (b & 0x10) ? "+" : "";
365                 const char* pinned   = (b & 0x08) ? "!" : "";
366 #ifdef _TARGET_ARM_
367                 const char* baseReg  = (b & 0x04) ? "R7" : "SP";
368 #else
369                 const char* baseReg  = (b & 0x04) ? "EBP" : "ESP";
370 #endif
371                 const char* sign     = (b & 0x02) ? "-" : "+";
372                 if (b & 0x01)
373                 {
374                     mask = VarInt::ReadUnsigned(pCursor);
375                 }
376 
377                 int c = 1;
378                 while (pInts != pCursor)
379                 {
380                     gcPrintf("%02x ", *pInts++);
381                     c++;
382                 }
383 
384                 for (; c < 4; c++)
385                 {
386                     gcPrintf("   ");
387                 }
388 
389                 gcPrintf("| 6  [%s%s%02X]%s%s\n", baseReg, sign, offset, interior, pinned);
390                 count++;
391 
392                 while (mask > 0)
393                 {
394                     offset += POINTER_SIZE;
395                     if (mask & 1)
396                     {
397                         if (!first)
398                             gcPrintf("      ");
399 
400                         gcPrintf("            |    [%s%s%02X]%s%s\n", baseReg, sign, offset, interior, pinned);
401                         count++;
402                     }
403                     mask >>= 1;
404                 }
405             }
406             break;
407         }
408     }
409     while (!last);
410 
411     //gcPrintf("\n");
412 }
413 
414 
415 
416 
DumpGCTable(PTR_UInt8 gcInfo,Tables * pTables,const GCInfoHeader & header)417 size_t   FASTCALL   GCDump::DumpGCTable (PTR_UInt8              gcInfo,
418                                          Tables *               pTables,
419                                          const GCInfoHeader&    header)
420 {
421     //
422     // Decode the method GC info
423     //
424     // 0ddddccc -- SMALL ENCODING
425     //
426     //              -- dddd is an index into the delta shortcut table
427     //              -- ccc is an offset into the callsite strings blob
428     //
429     // 1ddddddd { info offset } -- BIG ENCODING
430     //
431     //              -- ddddddd is a 7-bit delta
432     //              -- { info offset } is a variable-length unsigned encoding of the offset into the callsite
433     //                 strings blob for this callsite.
434     //
435     // 10000000 { delta } -- FORWARDER
436     //
437     //              -- { delta } is a variable-length unsigned encoding of the offset to the next callsite
438     //
439     // 11111111 -- STRING TERMINATOR
440     //
441 
442     PTR_UInt8 pCursor = gcInfo;
443     UInt32 curOffset = 0;
444 
445     for (;;)
446     {
447         UInt8 b = *pCursor++;
448         unsigned infoOffset;
449 
450         if (b & 0x80)
451         {
452             UInt8 lowBits = (b & 0x7F);
453             // FORWARDER
454             if (lowBits == 0)
455             {
456                 curOffset += VarInt::ReadUnsigned(pCursor);
457                 continue;
458             }
459             else
460             if (lowBits == 0x7F) // STRING TERMINATOR
461                 break;
462 
463             // BIG ENCODING
464             curOffset += lowBits;
465             infoOffset = VarInt::ReadUnsigned(pCursor);
466         }
467         else
468         {
469             // SMALL ENCODING
470             infoOffset = (b & 0x7);
471             curOffset += pTables->pbDeltaShortcutTable[b >> 3];
472         }
473 
474         DumpCallsiteString(curOffset, pTables->pbCallsiteInfoBlob + infoOffset, &header);
475     }
476 
477     gcPrintf("-------\n");
478 
479     return 0;
480 }
481 
482 #endif // _DEBUG || DACCESS_COMPILE
483