summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/Strike/sos.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/ToolBox/SOS/Strike/sos.h')
-rw-r--r--src/ToolBox/SOS/Strike/sos.h793
1 files changed, 793 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/sos.h b/src/ToolBox/SOS/Strike/sos.h
new file mode 100644
index 0000000000..aa8545e147
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/sos.h
@@ -0,0 +1,793 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+#pragma once
+
+#include "strike.h"
+#include "util.h"
+
+#ifndef SOS_Assert
+#ifdef _DEBUG
+#define SOS_Assert(x) do { if (!(x)) sos::Throw<sos::Exception>("SOS Assert Failure: %s\n", #x); } while(0)
+#else
+#define SOS_Assert(x) (void)0
+#endif
+#endif
+
+#ifdef throw
+#undef throw
+#endif
+
+#ifdef try
+#undef try
+#endif
+
+#ifdef catch
+#undef catch
+#endif
+
+class LinearReadCache;
+class CGCDesc;
+class CGCDescSeries;
+
+namespace sos
+{
+ class GCHeap;
+
+ /* The base SOS Exception. Note that most commands should not attempt to be
+ * resilient to exceptions thrown by most functions here. Instead a top level
+ * try/catch at the beginning of the command which prints out the exception's
+ * message should be sufficient.
+ * Note you should not throw these directly, instead use the sos::Throw function.
+ */
+ class Exception
+ {
+ public:
+ Exception(const char *format, va_list args)
+ {
+ vsprintf_s(mMsg, _countof(mMsg), format, args);
+
+ va_end(args);
+ }
+
+ inline virtual ~Exception() {}
+
+ // from std::exception
+ virtual const char *what() const
+ {
+ return mMsg;
+ }
+
+ const char *GetMesssage() const
+ {
+ return mMsg;
+ }
+
+ protected:
+ char mMsg[1024];
+ };
+
+ /* Thrown when we could not read data we expected out of the target process.
+ * This can be due to heap corruption, or it could just be an invalid pointer.
+ */
+ class DataRead : public Exception
+ {
+ public:
+ DataRead(const char *format, va_list args)
+ : Exception(format, args)
+ {
+ }
+ };
+
+ /* This is thrown when we detect heap corruption in the process.
+ */
+ class HeapCorruption : public Exception
+ {
+ public:
+ HeapCorruption(const char *format, va_list args)
+ : Exception(format, args)
+ {
+ }
+ };
+
+ // Internal helper method. Use SOS_Throw macros instead.
+ template <class T>
+ void Throw(const char *format, ...)
+ {
+ va_list args;
+ va_start(args, format);
+
+ throw T(format, args);
+ }
+
+ /* Checks to see if the user hit control-c. Throws an exception to escape SOS
+ * if so.
+ */
+ inline void CheckInterrupt()
+ {
+ if (g_ExtControl->GetInterrupt() == S_OK)
+ Throw<Exception>("User interrupt.");
+ }
+
+ /* ThinLock struct. Use Object::GetThinLock to fill the struct.
+ */
+ struct ThinLockInfo
+ {
+ int ThreadId;
+ TADDR ThreadPtr;
+ int Recursion;
+
+ ThinLockInfo()
+ : ThreadId(0), ThreadPtr(0), Recursion(0)
+ {
+ }
+ };
+
+ /* The MethodTable for an Object. The general pattern should be:
+ * MethodTable mt = someObject.GetMT();
+ */
+ class MethodTable
+ {
+ public:
+ /* Returns whether an object is from an AppDomain that has been unloaded.
+ * If so, we cannot validate the object's members.
+ * Params:
+ * mt - The address of the MethodTable to test for.
+ */
+ static bool IsZombie(TADDR mt);
+
+ /* Returns the method table for arrays.
+ */
+ inline static TADDR GetArrayMT()
+ {
+ return TO_TADDR(g_special_usefulGlobals.ArrayMethodTable);
+ }
+
+ /* Returns the method table for String objects.
+ */
+ inline static TADDR GetStringMT()
+ {
+ return TO_TADDR(g_special_usefulGlobals.StringMethodTable);
+ }
+
+ /* Returns the method table for Free objects.
+ */
+ inline static TADDR GetFreeMT()
+ {
+ return TO_TADDR(g_special_usefulGlobals.FreeMethodTable);
+ }
+
+ /* Returns true if the given method table is that of a Free object.
+ */
+ inline static bool IsFreeMT(TADDR mt)
+ {
+ return GetFreeMT() == mt;
+ }
+
+ /* Returns true if the given method table is that of an Array.
+ */
+ inline static bool IsArrayMT(TADDR mt)
+ {
+ return GetArrayMT() == mt;
+ }
+
+ /* Returns true if the given method table is that of a System.String object.
+ */
+ inline static bool IsStringMT(TADDR mt)
+ {
+ return GetStringMT() == mt;
+ }
+
+ inline static bool IsValid(TADDR mt)
+ {
+ DacpMethodTableData data;
+ return data.Request(g_sos, TO_CDADDR(mt)) == S_OK;
+ }
+
+ public:
+ MethodTable(TADDR mt)
+ : mMT(mt), mName(0)
+ {
+ }
+
+ MethodTable(const MethodTable &mt)
+ : mMT(mt.mMT), mName(mt.mName)
+ {
+ // Acquire the calculated mName field. Since we are making a copy, we will likely use
+ // the copy instead of the original.
+ mt.mName = NULL;
+ }
+
+ const MethodTable &operator=(const MethodTable &mt)
+ {
+ Clear();
+
+ // Acquire the calculated mName field. Since we are making a copy, we will likely use
+ // the copy instead of the original.
+ mMT = mt.mMT;
+ mName = mt.mName;
+ mt.mName = NULL;
+
+ return *this;
+ }
+
+ ~MethodTable()
+ {
+ Clear();
+ }
+
+ /* Returns the class name of this MethodTable. The pointer returned is
+ * valid through the lifetime of the MethodTable object and should not be
+ * freed.
+ */
+ const wchar_t *GetName() const;
+
+ private:
+ void Clear();
+
+ private:
+ TADDR mMT;
+ mutable wchar_t *mName;
+ };
+
+ /* This represents an object on the GC heap in the target process. This class
+ * represents a single object, and is immutable after construction. All
+ * information about this class is lazily evaluated, so it is entirely possible
+ * to get exceptions when calling any member function. If this is a concern,
+ * call validate before attempting to call any other method on this object.
+ */
+ class Object
+ {
+ public:
+ /* Attempts to determine if the target address points to a valid object.
+ * Note that this is a heuristic based check, so false positives could
+ * be possible.
+ * Params:
+ * address - The address of the object to inspect.
+ * verifyFields - Whether or not to validate that the fields the object
+ * points to are also valid. (If the object contains a
+ * corrupted pointer, passing true to this parameter will
+ * cause IsValid to return false.) In general passing
+ * true will make IsValid return less false positives.
+ */
+ static bool IsValid(TADDR address, bool verifyFields=false);
+
+ static int GetStringDataOffset()
+ {
+#ifndef _TARGET_WIN64_
+ return 8;
+#else
+ return 0xc;
+#endif
+ }
+
+ public:
+ /* Constructor. Use Object(TADDR, TADDR) instead if you know the method table.
+ * Parameters:
+ * addr - an address to an object on the managed heap
+ * Throws:
+ * Exception - if addr is misaligned.
+ */
+ Object(TADDR addr);
+
+ /* Constructor. Use this constructor if you already know the method table for
+ * the object in question. This will save a read if the method table is needed.
+ * Parameters:
+ * addr - an address to an object on the managed heap
+ * Throws:
+ * Exception - if addr is misaligned.
+ */
+ Object(TADDR addr, TADDR mt);
+
+ Object(const Object &rhs);
+
+ inline ~Object()
+ {
+ if (mMTData)
+ delete mMTData;
+
+ if (mTypeName)
+ delete mTypeName;
+ }
+
+ const Object &operator=(TADDR addr);
+
+ // Comparison operators. These compare the underlying address of
+ // the object to the parameter.
+ inline bool operator<=(TADDR addr) { return mAddress <= addr; }
+ inline bool operator>=(TADDR addr) { return mAddress >= addr; }
+ inline bool operator<(TADDR addr) { return mAddress < addr; }
+ inline bool operator>(TADDR addr) { return mAddress > addr; }
+ inline bool operator==(TADDR addr) { return mAddress == addr; }
+
+ /* Returns the target address of the object this represents.
+ */
+ inline TADDR GetAddress() const
+ {
+ return mAddress;
+ }
+
+ /* Returns the target address of the object this represents.
+ */
+ inline operator TADDR() const
+ {
+ return GetAddress();
+ }
+
+ /* Returns the object header for this object.
+ * Throws:
+ * DataRead - we failed to read the object header.
+ */
+ unsigned long GetHeader() const;
+
+ /* Gets the header for the current object, does not throw any exception.
+ * Params:
+ * outHeader - filled with the header if this function was successful.
+ * Returns:
+ * True if we successfully read the object header, false otherwise.
+ */
+ bool TryGetHeader(unsigned long &outHeader) const;
+
+ /* Returns the method table of the object this represents.
+ * Throws:
+ * DataRead - If we failed to read the method table from the address.
+ * This is usually indicative of heap corruption.
+ * HeapCorruption - If we successfully read the target method table
+ * but it is invalid. (We do not do a very deep
+ * verification here.)
+ */
+ TADDR GetMT() const;
+
+ /* Returns the component method table of the object. For example, if
+ * this object is an array, the method table will be the general array
+ * MT. Calling this function tells you what type of objects can be
+ * placed in the array.
+ * Throws:
+ * DataRead - If we failed to read the method table from the address.
+ * This is usually indicative of heap corruption.
+ * HeapCorruption - If we successfully read the target method table
+ * but it is invalid. (We do not do a very deep
+ * verification here.)
+ */
+ TADDR GetComponentMT() const;
+
+ /* Returns the size of the object this represents. Note that this size
+ * may not be pointer aligned.
+ * Throws:
+ * DataRead - If we failed to read the method table data (which contains
+ * the size of the object).
+ */
+ size_t GetSize() const;
+
+ /* Returns true if this object contains pointers to other objects.
+ * Throws:
+ * DataRead - if we failed to read out of the object's method table.
+ */
+ bool HasPointers() const;
+
+ /* Gets the thinlock information for this object.
+ * Params:
+ * out - The ThinLockInfo to be filled.
+ * Returns:
+ * True if the object has a thinlock, false otherwise. If this function
+ * returns false, then out will be untouched.
+ * Throws:
+ * DataRead - If we could not read the object header from the object.
+ */
+ bool GetThinLock(ThinLockInfo &out) const;
+
+ /* Returns true if this object is a Free object (meaning it points to free
+ * space in the GC heap.
+ * Throws:
+ * The same as GetMT().
+ */
+ inline bool IsFree() const
+ {
+ return GetMT() == MethodTable::GetFreeMT();
+ }
+
+ /* Returns true if this object is a string.
+ * Throws:
+ * The same as GetMT().
+ */
+ inline bool IsString() const
+ {
+ return GetMT() == MethodTable::GetStringMT();
+ }
+
+ /* Returns the length of the String, if this is a string object. This
+ * function assumes that you have called IsString first to ensure that
+ * the object is indeed a string.
+ * Throws:
+ * DataRead if we could not read the contents of the object.
+ */
+ size_t GetStringLength() const;
+
+ /* Fills the given buffer with the contents of the String. This
+ * function assumes you have called IsString first to ensure that this
+ * object is actually a System.String. This function does not throw,
+ * but the results are undefined if this object is not a string.
+ * Params:
+ * buffer - The buffer to fill with the string contents.
+ * size - The total size of the buffer.
+ * Returns:
+ * True if the string data was successfully requested and placed in
+ * buffer, false otherwise.
+ */
+ bool GetStringData(__out_ecount(size) wchar_t *buffer, size_t size) const;
+
+ /* Returns the name of the type of this object. E.g. System.String.
+ * Throws:
+ * DataRead if we could not read the contents of the object.
+ * Returns:
+ * A string containing the type of the object.
+ */
+ wchar_t *GetTypeName() const;
+
+ private:
+ void FillMTData() const;
+ void CalculateSizeAndPointers() const;
+ static bool VerifyMemberFields(TADDR pMT, TADDR obj);
+ static bool VerifyMemberFields(TADDR pMT, TADDR obj, WORD &numInstanceFields);
+
+ protected:
+ // Conceptually, this class is never modified after you pass in the the object address.
+ // That is, there can't be anything the user does to point this object to a different
+ // object after construction. Since we lazy evaluate *everything*, we must be able to
+ // modify these variables. Hence they are mutable.
+ TADDR mAddress;
+ mutable TADDR mMT;
+ mutable size_t mSize;
+ mutable bool mPointers;
+ mutable DacpMethodTableData *mMTData;
+ mutable wchar_t *mTypeName;
+ };
+
+ /* Enumerates all the GC references (objects) contained in an object. This uses the GCDesc
+ * map exactly as the GC does.
+ */
+ class RefIterator
+ {
+ public:
+ RefIterator(TADDR obj, LinearReadCache *cache = NULL);
+ RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache = NULL);
+ ~RefIterator();
+
+ /* Moves to the next reference in the object.
+ */
+ const RefIterator &operator++();
+
+ /* Returns the address of the current reference.
+ */
+ TADDR operator*() const;
+
+ /* Gets the offset into the object where the current reference comes from.
+ */
+ TADDR GetOffset() const;
+
+ /* Returns true if there are more objects in the iteration, false otherwise.
+ * Used as:
+ * if (itr)
+ * ...
+ */
+ inline operator void *() const
+ {
+ return (void*)!mDone;
+ }
+
+ private:
+ void Init();
+ inline TADDR ReadPointer(TADDR addr) const
+ {
+ if (mCache)
+ {
+ if (!mCache->Read(addr, &addr, false))
+ Throw<DataRead>("Could not read address %p.", addr);
+ }
+ else
+ {
+ MOVE(addr, addr);
+ }
+
+ return addr;
+ }
+
+ private:
+ LinearReadCache *mCache;
+ CGCDesc *mGCDesc;
+ bool mArrayOfVC, mDone;
+
+ TADDR *mBuffer;
+ CGCDescSeries *mCurrSeries;
+
+ int i, mCount;
+
+ TADDR mCurr, mStop, mObject;
+ size_t mObjSize;
+ };
+
+
+ /* The Iterator used to walk the managed objects on the GC heap.
+ * The general usage pattern for this class is:
+ * for (ObjectIterator itr = gcheap.WalkHeap(); itr; ++itr)
+ * itr->SomeObjectMethod();
+ */
+ class ObjectIterator
+ {
+ friend class GCHeap;
+ public:
+
+ /* Returns the next object in the GCHeap. Note that you must ensure
+ * that there are more objects to walk before calling this function by
+ * checking "if (iterator)". If this function throws an exception,
+ * the the iterator is invalid, and should no longer be used to walk
+ * the heap. This should generally only happen if we cannot read the
+ * MethodTable of the object to move to the next object.
+ * Throws:
+ * DataRead
+ */
+ const ObjectIterator &operator++();
+
+ /* Dereference operator. This allows you to take a reference to the
+ * current object. Note the lifetime of this reference is valid for
+ * either the lifetime of the iterator or until you call operator++,
+ * whichever is shorter. For example.
+ * void Foo(const Object &param);
+ * void Bar(const ObjectIterator &itr)
+ * {
+ * Foo(*itr);
+ * }
+ */
+ const Object &operator*() const;
+
+ /* Returns a pointer to the current Object to call members on it.
+ * The usage pattern for the iterator is to simply use operator->
+ * to call methods on the Object it points to without taking a
+ * direct reference to the underlying Object if at all possible.
+ */
+ const Object *operator->() const;
+
+ /* Returns false when the iterator has reached the end of the managed
+ * heap.
+ */
+ inline operator void *() const
+ {
+ return (void*)(mCurrHeap == mNumHeaps ? 0 : 1);
+ }
+
+ /* Do not use.
+ * TODO: Replace this functionality with int Object::GetGeneration().
+ */
+ bool IsCurrObjectOnLOH() const
+ {
+ SOS_Assert(*this);
+ return bLarge;
+ }
+
+ /* Verifies the current object. Returns true if the current object is valid.
+ * Returns false and fills 'buffer' with the reason the object is corrupted.
+ * This is a deeper validation than Object::IsValid as it checks the card
+ * table entires for the object in addition to the rest of the references.
+ * This function does not throw exceptions.
+ * Params:
+ * buffer - out buffer that is filled if and only if this function returns
+ * false.
+ * size - the total size of the buffer
+ * Returns:
+ * True if the object is valid, false otherwise.
+ */
+ bool Verify(__out_ecount(size) char *buffer, size_t size) const;
+
+ /* The same as Verify(char*, size_t), except it does not write out the failure
+ * reason to a provided buffer.
+ * See:
+ * ObjectIterator::Verify(char *, size_t)
+ */
+ bool Verify() const;
+
+ /* Attempts to move to the next object (similar to ObjectIterator++), but
+ * attempts to recover from any heap corruption by skipping to the next
+ * segment. If Verify returns false, meaning it detected heap corruption
+ * at the current object, you can use MoveToNextObjectCarefully instead of
+ * ObjectIterator++ to attempt to keep reading from the heap. If possible,
+ * this function attempts to move to the next object in the same segment,
+ * but if that's not possible then it skips to the next segment and
+ * continues from there.
+ * Note:
+ * This function can throw, and if it does then the iterator is no longer
+ * in a valid state. No further attempts to move to the next object will
+ * be possible.
+ * Throws:
+ * DataRead - if the heap is corrupted and it's not possible to continue
+ * walking the heap
+ */
+ void MoveToNextObjectCarefully();
+
+ private:
+ ObjectIterator(const DacpGcHeapDetails *heap, int numHeaps, TADDR start, TADDR stop);
+
+ bool VerifyObjectMembers(__out_ecount(size) char *buffer, size_t size) const;
+ void BuildError(__out_ecount(count) char *out, size_t count, const char *format, ...) const;
+
+ void AssertSanity() const;
+ bool NextSegment();
+ bool CheckSegmentRange();
+ void MoveToNextObject();
+
+ private:
+ DacpHeapSegmentData mSegment;
+ bool bLarge;
+ Object mCurrObj;
+ TADDR mLastObj, mStart, mEnd, mSegmentEnd;
+ AllocInfo mAllocInfo;
+ const DacpGcHeapDetails *mHeaps;
+ int mNumHeaps;
+ int mCurrHeap;
+ };
+
+ /* Reprensents an entry in the sync block table.
+ */
+ class SyncBlk
+ {
+ friend class SyncBlkIterator;
+ public:
+ /* Constructor.
+ * Params:
+ * index - the index of the syncblk entry you wish to inspect.
+ * This should be in range [1, MaxEntries], but in general
+ * you should always use the SyncBlk iterator off of GCHeap
+ * and not construct these directly.
+ * Throws:
+ * DataRead - if we could not read the syncblk entry for the given index.
+ */
+ explicit SyncBlk(int index);
+
+ /* Returns whether or not the current entry is a "Free" SyncBlk table entry
+ * or not. This should be called *before* any other function here.
+ */
+ bool IsFree() const;
+
+ /* Returns the address of this syncblk entry (generally for display purposes).
+ */
+ TADDR GetAddress() const;
+
+ /* Returns the address of the object which this is syncblk is pointing to.
+ */
+ TADDR GetObject() const;
+
+ /* Returns the index of this entry.
+ */
+ int GetIndex() const;
+
+ /* Returns the COMFlags for the SyncBlk object. The return value of this
+ * function is undefined if FEATURE_COMINTEROP is not defined, so you should
+ * #ifdef the calling region yourself.
+ */
+ DWORD GetCOMFlags() const;
+
+ unsigned int GetMonitorHeldCount() const;
+ unsigned int GetRecursion() const;
+ unsigned int GetAdditionalThreadCount() const;
+
+ /* Returns the thread which holds this monitor (this is the clr!Thread object).
+ */
+ TADDR GetHoldingThread() const;
+ TADDR GetAppDomain() const;
+
+ private:
+ /* Copy constructor unimplemented due to how expensive this is. Use references
+ * instead.
+ */
+ SyncBlk(const SyncBlk &rhs);
+ SyncBlk();
+ void Init();
+ const SyncBlk &operator=(int index);
+
+ private:
+ int mIndex;
+ DacpSyncBlockData mData;
+ };
+
+ /* An iterator over syncblks. The common usage for this class is:
+ * for (SyncBlkIterator itr; itr; ++itr)
+ * itr->SomeSyncBlkFunction();
+ */
+ class SyncBlkIterator
+ {
+ public:
+ SyncBlkIterator();
+
+ /* Moves to the next SyncBlk in the table.
+ */
+ inline const SyncBlkIterator &operator++()
+ {
+ SOS_Assert(mCurr <= mTotal);
+ mSyncBlk = ++mCurr;
+
+ return *this;
+ }
+
+ inline const SyncBlk &operator*() const
+ {
+ SOS_Assert(mCurr <= mTotal);
+ return mSyncBlk;
+ }
+
+ inline const SyncBlk *operator->() const
+ {
+ SOS_Assert(mCurr <= mTotal);
+ return &mSyncBlk;
+ }
+
+ inline operator void *() const
+ {
+ return (void*)(mCurr <= mTotal ? 1 : 0);
+ }
+
+ private:
+ int mCurr, mTotal;
+ SyncBlk mSyncBlk;
+ };
+
+ /* An class which contains information about the GCHeap.
+ */
+ class GCHeap
+ {
+ public:
+ static const TADDR HeapStart; // A constant signifying the start of the GC heap.
+ static const TADDR HeapEnd; // A constant signifying the end of the GC heap.
+
+ public:
+ /* Constructor.
+ * Throws:
+ * DataRead
+ */
+ GCHeap();
+
+ /* Returns an ObjectIterator which allows you to walk the objects on the managed heap.
+ * This ObjectIterator is valid for the duration of the GCHeap's lifetime. Note that
+ * if you specify an address at which you wish to start walking the heap it need
+ * not point directly to a managed object. However, if it does not, WalkHeap
+ * will need to walk the segment that address resides in to find the first object
+ * after that address, and if it encounters any heap corruption along the way,
+ * it may be impossible to walk the heap from the address specified.
+ *
+ * Params:
+ * start - The starting address at which you want to start walking the heap.
+ * This need not point directly to an object on the heap.
+ * end - The ending address at which you want to stop walking the heap. This
+ * need not point directly to an object on the heap.
+ * validate - Whether or not you wish to validate the GC heap as you walk it.
+ * Throws:
+ * DataRead
+ */
+ ObjectIterator WalkHeap(TADDR start = HeapStart, TADDR stop = HeapEnd) const;
+
+ /* Returns true if the GC Heap structures are in a valid state for traversal.
+ * Returns false if not (e.g. if we are in the middle of a relocation).
+ */
+ bool AreGCStructuresValid() const;
+
+ private:
+ DacpGcHeapDetails *mHeaps;
+ DacpGcHeapData mHeapData;
+ int mNumHeaps;
+ };
+
+ // convenience functions
+ /* A temporary wrapper function for Object::IsValid. There are too many locations
+ * in SOS which need to use IsObject but have a wide variety of internal
+ * representations for an object address. Until it can all be unified as TADDR,
+ * this is what they will use.
+ */
+ template <class T>
+ bool IsObject(T addr, bool verifyFields=false)
+ {
+ return Object::IsValid(TO_TADDR(addr), verifyFields);
+ }
+
+
+ void BuildTypeWithExtraInfo(TADDR addr, unsigned int size, __inout_ecount(size) wchar_t *buffer);
+}