#endif // SOS_TARGET_ARM64
+//
+// GCEncodingInfo class member implementations
+//
+
+bool GCEncodingInfo::Initialize()
+{
+ buf = nullptr;
+ cchBufAllocation = 0;
+ cchBuf = 0;
+ curPtr = nullptr;
+ done = false;
+ hotSizeToAdd = 0;
+
+ return ReallocBuf();
+}
+
+void GCEncodingInfo::Deinitialize()
+{
+ delete[] buf;
+ buf = nullptr;
+ cchBufAllocation = 0;
+ cchBuf = 0;
+ curPtr = nullptr;
+ done = false;
+ hotSizeToAdd = 0;
+}
+
+bool GCEncodingInfo::ReallocBuf()
+{
+ size_t newSize;
+ if (!ClrSafeInt<size_t>::multiply(cchBufAllocation, 2, newSize))
+ {
+ ExtOut("<integer overflow>\n");
+ return false;
+ }
+
+ newSize = _max(1000, newSize);
+ char* newbuffer = new char[newSize];
+ if (newbuffer == NULL)
+ {
+ ExtOut("Could not allocate memory for the gc info dump.\n");
+ return false;
+ }
+
+ if (buf != nullptr)
+ {
+ memcpy(newbuffer, buf, cchBufAllocation);
+ delete[] buf;
+ }
+ buf = newbuffer;
+ cchBufAllocation = newSize;
+
+ // Make sure it is null terminated. It should already be, unless this is the first time.
+ buf[cchBuf] = '\0';
+
+ return true;
+}
+bool GCEncodingInfo::EnsureAdequateBufferSpace(SIZE_T count)
+{
+ while (cchBuf + count + 1 > cchBufAllocation) // +1 for null terminator
+ {
+ if (!ReallocBuf())
+ return false;
+ }
+
+ return true;
+}
+
+void GCEncodingInfo::DumpGCInfoThrough(SIZE_T curOffset)
+{
+ if (done)
+ {
+ // We've already output all the GC info
+ return;
+ }
+
+ if (curPtr == nullptr)
+ {
+ // We're just starting iteration.
+ curPtr = buf;
+ }
+
+ for (;;)
+ {
+ char *pNewLine = strchr(curPtr, '\n');
+ if (pNewLine != nullptr)
+ *pNewLine = '\0';
+ SIZE_T cchLine = strlen(curPtr);
+
+ // There are two kinds of lines: those that start with an offset, and
+ // those that don't. It should be the case that all lines without
+ // offset come first, but that's certainly not guaranteed.
+ //
+ // For the lines with offset, it will be a 16-bit (x86) or 32-bit (non-x86) hex
+ // offset. strtoul returns ULONG_MAX or 0 on failure. 0 is a valid
+ // offset for the first encoding.
+
+ char *pEnd;
+ ULONG ofs = strtoul(curPtr, &pEnd, /* base */ 16);
+
+ if ((pEnd != curPtr) && isspace(*pEnd) && (ULONG_MAX != ofs))
+ {
+ // We got a number. Assume it's an offset.
+ if (ofs <= curOffset)
+ {
+ ExtOut(curPtr);
+ ExtOut("\n");
+ curPtr += cchLine + 1; // +1 to pass the terminating null
+ }
+ else
+ {
+ // We'll come back and output this one later. Restore the newline, if any.
+ // Leave curPtr alone.
+ if (pNewLine != nullptr)
+ *pNewLine = '\n';
+ break;
+ }
+ }
+ else
+ {
+ // This is something else. Output it.
+ ExtOut(curPtr);
+ ExtOut("\n");
+ curPtr += cchLine + 1; // +1 to pass the terminating null
+ }
+
+ if (pNewLine == nullptr)
+ {
+ // There was no trailing newline, and we output some text; we must be done
+ // (if we didn't output any text, we wouldn't get here).
+ done = true;
+ break;
+ }
+ else if (*curPtr == '\0')
+ {
+ // We must have output a line with a newline, and now we're pointing at the
+ // trailing null.
+ done = true;
+ break;
+ }
+
+ // Otherwise, we have text left to output.
+ }
+}
return Status;
}
-#if !defined(FEATURE_PAL)
+GCEncodingInfo g_gcEncodingInfo; // The constructor should run to create the initial buffer allocation.
void DecodeGCTableEntry (const char *fmt, ...)
{
- GCEncodingInfo *pInfo = (GCEncodingInfo*)GetFiberData();
va_list va;
//
- // Append the new data to the buffer
+ // Append the new data to the buffer. If it doesn't fit, allocate a new buffer that is bigger and try again.
//
va_start(va, fmt);
- int cch = _vsnprintf_s(&pInfo->buf[pInfo->cch], _countof(pInfo->buf) - pInfo->cch, _countof(pInfo->buf) - pInfo->cch - 1, fmt, va);
- if (cch >= 0)
- pInfo->cch += cch;
-
- va_end(va);
-
- pInfo->buf[pInfo->cch] = '\0';
-
- //
- // If there are complete lines in the buffer, decode them.
- //
-
- for (;;)
+ // Make sure there's at least a minimum amount of free space in the buffer. We need to minimally
+ // ensure that 'maxCchToWrite' is >0. 20 is an arbitrary smallish number.
+ if (!g_gcEncodingInfo.EnsureAdequateBufferSpace(20))
{
- char *pNewLine = strchr(pInfo->buf, '\n');
-
- if (!pNewLine)
- break;
+ ExtOut("Could not allocate memory for GC info\n");
+ return;
+ }
- //
- // The line should start with a 16-bit (x86) or 32-bit (non-x86) hex
- // offset. strtoul returns ULONG_MAX or 0 on failure. 0 is a valid
- // offset for the first encoding, or while the last offset was 0.
- //
+ while (true)
+ {
+ char* buffer = &g_gcEncodingInfo.buf[g_gcEncodingInfo.cchBuf];
+ size_t sizeOfBuffer = g_gcEncodingInfo.cchBufAllocation - g_gcEncodingInfo.cchBuf;
+ size_t maxCchToWrite = sizeOfBuffer - 1; // -1 to leave space for the null terminator
+ int cch = _vsnprintf_s(buffer, sizeOfBuffer, maxCchToWrite, fmt, va);
- if (isxdigit(pInfo->buf[0]))
+ // cch == -1 should be the only negative result, but checking < 0 is defensive in case some runtime returns something else.
+ // We should also check "errno == ERANGE", but it seems that some runtimes don't set that properly.
+ if (cch < 0)
{
- char *pEnd;
- ULONG ofs = strtoul(pInfo->buf, &pEnd, 16);
-
- if ( isspace(*pEnd)
- && -1 != ofs
- && ( -1 == pInfo->ofs
- || 0 == pInfo->ofs
- || ofs > 0))
+ if (sizeOfBuffer > 1000)
{
- pInfo->ofs = ofs;
- *pNewLine = '\0';
-
- SwitchToFiber(pInfo->pvMainFiber);
+ // There must be some unexpected problem if we can't write the GC info into such a large buffer, so bail.
+ ExtOut("Error generating GC info\n");
+ break;
}
+ else if (!g_gcEncodingInfo.ReallocBuf())
+ {
+ // We couldn't reallocate the buffer; skip the rest of the text.
+ ExtOut("Could not allocate memory for GC info\n");
+ break;
+ }
+
+ // If we get here, we successfully reallocated the buffer larger, so we'll try again to write this entry
+ // into the larger buffer.
}
- else if (0 == strncmp(pInfo->buf, "Untracked:", 10))
+ else
{
- pInfo->ofs = 0;
- *pNewLine = '\0';
-
- SwitchToFiber(pInfo->pvMainFiber);
+ // We successfully added this entry to the GC info we're accumulating.
+ // cch is the number of characters written, not including the terminating null.
+ g_gcEncodingInfo.cchBuf += cch;
+ break;
}
-
- //
- // Shift the remaining data to the start of the buffer
- //
-
- strcpy_s(pInfo->buf, _countof(pInfo->buf), pNewLine+1);
- pInfo->cch = (int)strlen(pInfo->buf);
}
-}
-
-VOID CALLBACK DumpGCTableFiberEntry (LPVOID pvGCEncodingInfo)
-{
- GCEncodingInfo *pInfo = (GCEncodingInfo*)pvGCEncodingInfo;
- GCInfoToken gcInfoToken = { pInfo->table, GCINFO_VERSION };
- g_targetMachine->DumpGCInfo(gcInfoToken, pInfo->methodSize, DecodeGCTableEntry, false /*encBytes*/, false /*bPrintHeader*/);
-
- pInfo->fDoneDecoding = true;
- SwitchToFiber(pInfo->pvMainFiber);
+ va_end(va);
}
-#endif // !FEATURE_PAL
BOOL gatherEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID token)
{
CMDOption option[] =
{ // name, vptr, type, hasValue
-#ifndef FEATURE_PAL
{"-gcinfo", &fWithGCInfo, COBOOL, FALSE},
-#endif
{"-ehinfo", &fWithEHInfo, COBOOL, FALSE},
{"-n", &bSuppressLines, COBOOL, FALSE},
{"-o", &bDisplayOffsets, COBOOL, FALSE},
ExtOut("Begin %p, size %x\n", SOS_PTR(codeHeaderData.MethodStart), codeHeaderData.MethodSize);
}
-#if !defined(FEATURE_PAL)
//
- // Set up to mix gc info with the code if requested
+ // Set up to mix gc info with the code if requested. To do this, we first generate all the textual
+ // gc info up front. This text is the same as the "!gcinfo" command, and looks like:
//
-
- GCEncodingInfo gcEncodingInfo = {0};
+ // Prolog size: 0
+ // Security object: <none>
+ // GS cookie: <none>
+ // PSPSym: <none>
+ // Generics inst context: <none>
+ // PSP slot: <none>
+ // GenericInst slot: <none>
+ // Varargs: 0
+ // Frame pointer: rbp
+ // Wants Report Only Leaf: 0
+ // Size of parameter area: 20
+ // Return Kind: Scalar
+ // Code size: 1ec
+ // Untracked: +rbp-10 +rbp-30 +rbp-48 +rbp-50 +rbp-58 +rbp-60 +rbp-68 +rbp-70
+ // 0000001e interruptible
+ // 0000003c +rax
+ // 0000004d +rdx
+ // 00000051 +rcx
+ // 00000056 -rdx -rcx -rax
+ // 0000005a +rcx
+ // 00000067 -rcx
+ // 00000080 +rcx
+ // 00000085 -rcx
+ // 0000009e +rcx
+ // 000000a3 -rcx
+ // 000000bc +rcx
+ // 000000c1 -rcx
+ // 000000d7 +rcx
+ // 000000e5 -rcx
+ // 000000ef +rax
+ // 0000010a +r8
+ // 00000119 +rcx
+ // 00000120 -r8 -rcx -rax
+ // 0000012f +rax
+ // 00000137 +r8
+ // 00000146 +rcx
+ // 00000150 -r8 -rcx -rax
+ // 0000015f +rax
+ // 00000167 +r8
+ // 00000176 +rcx
+ // 00000180 -r8 -rcx -rax
+ // 0000018f +rax
+ // 00000197 +r8
+ // 000001a6 +rcx
+ // 000001b0 -r8 -rcx -rax
+ // 000001b4 +rcx
+ // 000001b8 +rdx
+ // 000001bd -rdx -rcx
+ // 000001c8 +rcx
+ // 000001cd -rcx
+ // 000001d2 +rcx
+ // 000001d7 -rcx
+ // 000001e5 not interruptible
+ //
+ // For the entries without offset prefixes, we output them before the first offset of code.
+ // (Previously, we only displayed the "Untracked:" element, but displaying all this additional
+ // GC info is useful, and then the user doesn't need to also do a "!gcinfo" to see it.)
+ // For the entries with offset prefixes, we parse the offset, and display all relevant information
+ // before the current instruction offset being disassembled, that is, all the lines of GC info
+ // with an offset greater than the previous instruction and with an offset less than or equal
+ // to the offset of the current instruction.
// The actual GC Encoding Table, this is updated during the course of the function.
- gcEncodingInfo.table = NULL;
-
- // The holder to make sure we clean up the memory for the table
+ // Use a holder to make sure we clean up the memory for the table.
ArrayHolder<BYTE> table = NULL;
if (fWithGCInfo)
return E_FAIL;
}
-
// Assign the new array to the mutable gcEncodingInfo table and to the
// table ArrayHolder to clean this up when the function exits.
- table = gcEncodingInfo.table = new NOTHROW BYTE[tableSize];
-
- if (gcEncodingInfo.table == NULL)
+ table = new NOTHROW BYTE[tableSize];
+ if (table == NULL)
{
ExtOut("Could not allocate memory to read the gc info.\n");
return E_OUTOFMEMORY;
}
- memset (gcEncodingInfo.table, 0, tableSize);
+ memset (table, 0, tableSize);
// We avoid using move here, because we do not want to return
- if (!SafeReadMemory(TO_TADDR(codeHeaderData.GCInfo), gcEncodingInfo.table, tableSize, NULL))
+ if (!SafeReadMemory(TO_TADDR(codeHeaderData.GCInfo), table, tableSize, NULL))
{
ExtOut("Could not read memory %p\n", SOS_PTR(codeHeaderData.GCInfo));
return Status;
//
// Skip the info header
//
- gcEncodingInfo.methodSize = (unsigned int)codeHeaderData.MethodSize;
+ unsigned int methodSize = (unsigned int)codeHeaderData.MethodSize;
- //
- // DumpGCTable will call gcPrintf for each encoding. We'd like a "give
- // me the next encoding" interface, but we're stuck with the callback.
- // To reconcile this without messing up too much code, we'll create a
- // fiber to dump the gc table. When we need the next gc encoding,
- // we'll switch to this fiber. The callback will note the next offset,
- // and switch back to the main fiber.
- //
-
- gcEncodingInfo.ofs = -1;
- gcEncodingInfo.hotSizeToAdd = 0;
-
- gcEncodingInfo.pvMainFiber = ConvertThreadToFiber(NULL);
- if (!gcEncodingInfo.pvMainFiber && ERROR_ALREADY_FIBER == GetLastError())
- gcEncodingInfo.pvMainFiber = GetCurrentFiber();
+ if (!g_gcEncodingInfo.Initialize())
+ {
+ return E_OUTOFMEMORY;
+ }
- if (!gcEncodingInfo.pvMainFiber)
- return Status;
-
- gcEncodingInfo.pvGCTableFiber = CreateFiber(0, DumpGCTableFiberEntry, &gcEncodingInfo);
- if (!gcEncodingInfo.pvGCTableFiber)
- return Status;
-
- SwitchToFiber(gcEncodingInfo.pvGCTableFiber);
+ GCInfoToken gcInfoToken = { table, GCINFO_VERSION };
+ g_targetMachine->DumpGCInfo(gcInfoToken, methodSize, DecodeGCTableEntry, false /*encBytes*/, false /*bPrintHeader*/);
}
-#endif
SOSEHInfo *pInfo = NULL;
if (fWithEHInfo)
((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.MethodSize,
dwStartAddr,
(DWORD_PTR) MethodDescData.GCStressCodeCopy,
-#if !defined(FEATURE_PAL)
- fWithGCInfo ? &gcEncodingInfo :
-#endif
- NULL,
+ fWithGCInfo ? &g_gcEncodingInfo : NULL,
pInfo,
bSuppressLines,
bDisplayOffsets
((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.HotRegionSize,
dwStartAddr,
(DWORD_PTR) MethodDescData.GCStressCodeCopy,
-#if !defined(FEATURE_PAL)
- fWithGCInfo ? &gcEncodingInfo :
-#endif
- NULL,
+ fWithGCInfo ? &g_gcEncodingInfo : NULL,
pInfo,
bSuppressLines,
bDisplayOffsets
ExtOut("Cold region:\n");
-#if !defined(FEATURE_PAL)
// Displaying gcinfo for a cold region requires knowing the size of
// the hot region preceeding.
- gcEncodingInfo.hotSizeToAdd = codeHeaderData.HotRegionSize;
-#endif
+ g_gcEncodingInfo.hotSizeToAdd = codeHeaderData.HotRegionSize;
+
g_targetMachine->Unassembly (
(DWORD_PTR) codeHeaderData.ColdRegionStart,
((DWORD_PTR)codeHeaderData.ColdRegionStart) + codeHeaderData.ColdRegionSize,
dwStartAddr,
((DWORD_PTR) MethodDescData.GCStressCodeCopy) + codeHeaderData.HotRegionSize,
-#if !defined(FEATURE_PAL)
- fWithGCInfo ? &gcEncodingInfo :
-#endif
- NULL,
+ fWithGCInfo ? &g_gcEncodingInfo : NULL,
pInfo,
bSuppressLines,
bDisplayOffsets
delete pInfo;
pInfo = NULL;
}
-
-#if !defined(FEATURE_PAL)
- if (fWithGCInfo)
- DeleteFiber(gcEncodingInfo.pvGCTableFiber);
-#endif
+ if (fWithGCInfo)
+ {
+ g_gcEncodingInfo.Deinitialize();
+ }
+
return Status;
}