Skip to content

Instantly share code, notes, and snippets.

@bartlomiejmucha
Created April 27, 2019 13:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bartlomiejmucha/4e979d7d22b72da7358c062a245b8ec1 to your computer and use it in GitHub Desktop.
Save bartlomiejmucha/4e979d7d22b72da7358c062a245b8ec1 to your computer and use it in GitHub Desktop.
Use it to debug serialization exceptions in Sitecore. Set it as redis provider for private and shared sessions.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
using System.Web.SessionState;
using Sitecore.Diagnostics;
using StackExchange.Redis;
namespace Example
{
public class CustomRedisProvider : Sitecore.SessionProvider.Redis.RedisSessionStateProvider
{
private static readonly string WriteLockAndGetExpiredDataScript = string.Format("\r\n local retArray = {{}} \r\n retArray[1] = ''\r\n retArray[2] = {{}}\r\n retArray[3] = '-1'\r\n \r\n local ExpirationTime = redis.call('HGET', KEYS[2], '{1}')\r\n if ExpirationTime ~= false and ExpirationTime <= ARGV[1] then\r\n redis.call('HSET', KEYS[2], '{2}', ARGV[2])\r\n redis.call('HSET', KEYS[2], '{1}', ARGV[3])\r\n\r\n retArray[2] = redis.call('HGETALL',KEYS[1])\r\n local SessionTimeout = redis.call('HGET', KEYS[2], '{0}')\r\n if SessionTimeout ~= false then \r\n retArray[3] = SessionTimeout \r\n end\r\n end\r\n\r\n return retArray\r\n ", (object)"SessionTimeout", (object)"ExpirationTime", (object)"WriteLock");
protected override string OnProcessExpiredItems(DateTime signalTime)
{
if (signalTime > DateTime.UtcNow)
signalTime = DateTime.UtcNow;
try
{
var cacheFieldInfo = this.GetType().BaseType.GetField("cache", BindingFlags.NonPublic | BindingFlags.Instance);
var cache = cacheFieldInfo.GetValue(this);
var methodInfo = cache.GetType().GetMethod("GetExpirationCandidateSessionIds");
var methodInfo2 = cache.GetType().GetMethod("CheckAndUpdateDataExpirationTime");
//var methodInfo3 = cache.GetType().GetMethod("TakeWriteLockAndGetExpiredData");
var methodInfo4 = cache.GetType().GetMethod("TryRemoveAndReleaseLockIfLockIdMatch");
var redisConnectionInfo = cache.GetType()
.GetField("redisConnection", BindingFlags.NonPublic | BindingFlags.Instance);
var redisConnection = redisConnectionInfo.GetValue(cache);
var methodInfoR1 = redisConnection.GetType().GetMethod("Eval");
var methodInfoR2 = redisConnection.GetType().GetMethod("GetSessionData");
var methodInfoR3 = redisConnection.GetType().GetMethod("GetSessionTimeout");
var configurationFieldInfo = this.GetType().BaseType.GetField("configuration", BindingFlags.NonPublic | BindingFlags.Instance);
var configuration = configurationFieldInfo.GetValue(this);
var propertyInfo = configuration.GetType().GetProperty("PollingExpirationTimeout");
var pollingExpirationTimeout = (int)propertyInfo.GetValue(configuration);
var propertyInfo2 = configuration.GetType().GetProperty("ApplicationName");
var applicationName = (string)propertyInfo2.GetValue(configuration);
foreach (string candidateSessionId in (IEnumerable<string>)methodInfo.Invoke(cache, new object[] { signalTime }))
{
DateTime utcNow = DateTime.UtcNow;
ISessionStateItemCollection data;
int sessionTimeout;
//var params3 = new object[]
//{
// candidateSessionId, signalTime, utcNow,
// pollingExpirationTimeout, null /*data*/, null /* sessionTimeout*/
//};
if ((bool)methodInfo2.Invoke(cache, new object[] { candidateSessionId, signalTime }) /*&& (bool)methodInfo3.Invoke(cache, params3)*/)
{
var sessionId = candidateSessionId;
var lockTime = utcNow;
var expirationTimeout = pollingExpirationTimeout;
KeyGenerator keys = KeyGenerator.GetKeys(sessionId, applicationName);
string str = lockTime.Ticks.ToString();
DateTime dateTime = DateTime.UtcNow.AddSeconds((double)expirationTimeout);
string[] keyArgs = new string[2]
{
keys.DataKey,
keys.InternalKey
};
object[] valueArgs = new object[3]
{
(object) (signalTime.Ticks / 10000L),
(object) str,
(object) (dateTime.Ticks / 10000L)
};
var rowDataFromRedis = methodInfoR1.Invoke(redisConnection, new object[] { WriteLockAndGetExpiredDataScript, keyArgs, valueArgs });
try
{
data = (ISessionStateItemCollection)methodInfoR2.Invoke(redisConnection, new object[] { rowDataFromRedis });
sessionTimeout = (int)methodInfoR3.Invoke(redisConnection, new object[] { rowDataFromRedis });
//object rowDataFromRedis = this.redisConnection.Eval(RedisConnectionWrapper.WriteLockAndGetExpiredDataScript, keyArgs, valueArgs);
//data = this.redisConnection.GetSessionData(rowDataFromRedis);
//sessionTimeout = this.redisConnection.GetSessionTimeout(rowDataFromRedis);
if (data != null)
{
//data = (ISessionStateItemCollection)params3[4];
//sessionTimeout = (int)params3[5];
SessionStateStoreData sessionStateStoreData = new SessionStateStoreData(data, new HttpStaticObjectsCollection(), sessionTimeout);
try
{
this.ExecuteSessionEnd(candidateSessionId, sessionStateStoreData);
}
finally
{
methodInfo4.Invoke(cache,
new object[] { candidateSessionId, (object)utcNow.Ticks.ToString() });
}
}
}
catch (Exception e)
{
Log.Error($"Custom Redis Provider: {e.ToString()}", this);
RedisResult[] redisResultArray1 = (RedisResult[])((RedisResult)rowDataFromRedis);
ISessionStateItemCollection stateItemCollection = (ISessionStateItemCollection)null;
if (redisResultArray1.Length > 1 && redisResultArray1[1] != null)
{
RedisResult[] redisResultArray2 = (RedisResult[])redisResultArray1[1];
if (redisResultArray2 != null && redisResultArray2.Length != 0 && redisResultArray2.Length % 2 == 0)
{
if (!((string)redisResultArray2[0] == "EmptyData" &&
(string)redisResultArray2[1] == "1"))
{
for (int index1 = 0; index1 + 1 < redisResultArray2.Length; index1 += 2)
{
string index2 = (string)redisResultArray2[index1];
try
{
object objectFromBytes =
GetObjectFromBytes((byte[])redisResultArray2[index1 + 1]);
}
catch (Exception exception)
{
Log.Error($"Custom Redis Provider:error {exception}", this);
}
}
}
}
}
Log.Error($"Custom Redis Provider:end", this);
throw;
}
}
}
}
catch (Exception ex)
{
Log.Error("ProcessExpiredItems => {0}", (object)ex.ToString());
this.LastException = ex;
throw;
}
return (string)null;
}
internal static object GetObjectFromBytes(byte[] dataAsBytes)
{
if (dataAsBytes == null)
return (object)null;
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (MemoryStream memoryStream = new MemoryStream(dataAsBytes, 0, dataAsBytes.Length))
{
object obj;
using (BinaryReader binaryReader = new BinaryReader((Stream)memoryStream))
{
if (binaryReader.ReadBoolean())
{
using (DeflateStream deflateStream = new DeflateStream((Stream)memoryStream, CompressionMode.Decompress))
{
obj = Deserialize((Stream)deflateStream, binaryFormatter);
}
}
else
{
obj = Deserialize((Stream)memoryStream, binaryFormatter);
}
}
if (obj == null || obj.GetType().Name == "RedisNull")
return (object)null;
return obj;
}
}
private static object Deserialize(Stream stream, BinaryFormatter binaryFormatter)
{
var binaryParserType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly
.GetType("System.Runtime.Serialization.Formatters.Binary.__BinaryParser");
var objectReaderType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly
.GetType("System.Runtime.Serialization.Formatters.Binary.ObjectReader");
var internalFEType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly
.GetType("System.Runtime.Serialization.Formatters.Binary.InternalFE");
var internalFEObj = Activator.CreateInstance(internalFEType);
internalFEType.GetField("FEtypeFormat", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj,
binaryFormatter.GetType().GetField("m_typeFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter));
internalFEType.GetField("FEserializerTypeEnum", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj, 2);
internalFEType.GetField("FEassemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj,
binaryFormatter.GetType().GetField("m_assemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter));
internalFEType.GetField("FEsecurityLevel", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj,
binaryFormatter.GetType().GetField("m_securityLevel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter));
var objectReaderConstructor = objectReaderType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
var objectReaderObj = objectReaderConstructor.Invoke(new object[]
{
stream,
binaryFormatter.GetType().GetField("m_surrogates", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter),
binaryFormatter.GetType().GetField("m_context", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter),
internalFEObj,
binaryFormatter.GetType().GetField("m_binder", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter),
});
objectReaderObj.GetType().GetField("crossAppDomainArray", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj,
binaryFormatter.GetType().GetField("m_crossAppDomainArray", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter));
objectReaderObj.GetType().GetField("bFullDeserialization", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false);
objectReaderObj.GetType().GetProperty("TopObject", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, (object)null);
objectReaderObj.GetType().GetField("topId", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, 0L);
objectReaderObj.GetType().GetField("bMethodCall", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false);
objectReaderObj.GetType().GetField("bMethodReturn", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false);
objectReaderObj.GetType().GetField("bIsCrossAppDomain", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false);
objectReaderObj.GetType().GetField("bSimpleAssembly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, (int)(binaryFormatter.GetType().GetField("m_assemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)) == 0);
var binaryParserConstructor = binaryParserType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
var binaryParserObj = binaryParserConstructor.Invoke(new object[] {stream, objectReaderObj });
objectReaderObj.GetType().GetField("handler", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, null);
binaryParserObj.GetType().GetMethod("Run", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(binaryParserObj, new object[] {});
var bowmt = binaryParserObj.GetType().GetField("bowmt", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(binaryParserObj);
var objectString = binaryParserObj.GetType().GetField("objectString", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(binaryParserObj);
object name = "";
if (bowmt != null)
{
name = bowmt.GetType().GetField("name", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(bowmt);
}
object value = "";
if (objectString != null)
{
value = objectString.GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(objectString);
}
Log.Error($"Custom Redis Provider: bowmt: {name}; objectString: {value}", binaryFormatter);
// NEXT STEP
var bFullDeserialization = objectReaderObj.GetType().GetField("bFullDeserialization", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(objectReaderObj);
if ((bool) bFullDeserialization == true)
{
var objectManagerObj = objectReaderObj.GetType().GetField("m_objectManager", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(objectReaderObj);
var specialFixupObjectsObj = objectManagerObj.GetType()
.GetProperty("SpecialFixupObjects", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(objectManagerObj);
var fixupEnumeratorObj = specialFixupObjectsObj.GetType()
.GetMethod("GetFixupEnumerator", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(specialFixupObjectsObj, new Object []{});
int num = -1;
while (num != 0)
{
num = 0;
while ((bool)fixupEnumeratorObj.GetType()
.GetMethod("MoveNext", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(fixupEnumeratorObj, new object[0]))
{
var currentObjectHolderObj = fixupEnumeratorObj.GetType()
.GetProperty("Current", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(fixupEnumeratorObj);
var currentObjectValueObj = currentObjectHolderObj.GetType()
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
if (currentObjectValueObj == null)
throw new SerializationException("Serialization_ObjectNotSupplied");
var totalDependentObjects = currentObjectHolderObj.GetType()
.GetProperty("TotalDependentObjects", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
if ((int)totalDependentObjects == 0)
{
var requiresSerInfoFixup = currentObjectHolderObj.GetType()
.GetProperty("RequiresSerInfoFixup", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
var isIncompleteObjectReference = (bool)currentObjectHolderObj.GetType()
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
if ((bool)requiresSerInfoFixup)
{
objectManagerObj.GetType()
.GetMethod("FixupSpecialObject", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj });
++num;
}
else if (!isIncompleteObjectReference)
{
objectManagerObj.GetType()
.GetMethod("CompleteObject", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj, true });
}
isIncompleteObjectReference = (bool)currentObjectHolderObj.GetType()
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
if (isIncompleteObjectReference)
{
int numA = 0;
try
{
object objectValueTemp;
object objectValueObj;
do
{
objectValueTemp = currentObjectHolderObj.GetType()
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
var mContextObj = (StreamingContext)objectManagerObj.GetType()
.GetField("m_context", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(objectManagerObj);
var objectReference = ((IObjectReference) currentObjectValueObj);
var reflectedTypeObj = (TypeInfo)objectReference.GetType().GetField("m_reflectedType",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(objectReference);
Log.Error($"Custom Redis Provider: reflected type: {reflectedTypeObj.ToString()}.", binaryFormatter);
var realObjectObj = objectReference.GetRealObject(mContextObj);
currentObjectHolderObj.GetType()
.GetMethod("SetObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(currentObjectHolderObj, new object[] { realObjectObj, objectManagerObj });
objectValueObj = currentObjectHolderObj.GetType()
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
if (objectValueObj == null)
{
currentObjectHolderObj.GetType()
.GetMethod("SetObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(currentObjectHolderObj, new object[] { objectManagerObj });
objectValueObj = currentObjectHolderObj.GetType()
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentObjectHolderObj);
}
if (numA++ == 100)
throw new SerializationException("Serialization_TooManyReferences");
if (!(objectValueObj is IObjectReference))
break;
}
while (objectValueTemp != objectValueObj);
}
catch (NullReferenceException ex)
{
}
currentObjectHolderObj.GetType()
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(currentObjectHolderObj, false);
objectManagerObj.GetType()
.GetMethod("DoNewlyRegisteredObjectFixups", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj });
++num;
}
}
}
}
}
return null;
}
}
class KeyGenerator
{
private string id;
public string DataKey { get; private set; }
public string InternalKey { get; private set; }
public KeyGenerator(string id, string applicationName)
{
this.id = id;
this.DataKey = "{" + applicationName + "_" + id + "}_Data";
this.InternalKey = "{" + applicationName + "_" + id + "}_Internal";
}
public void RegenerateKeyStringIfIdModified(string id, string applicationName)
{
if (id.Equals(this.id))
return;
this.id = id;
this.DataKey = "{" + applicationName + "_" + id + "}_Data";
this.InternalKey = "{" + applicationName + "_" + id + "}_Internal";
}
public static string ExtractId(string key)
{
int startIndex1 = key.IndexOf("}_", StringComparison.Ordinal);
int startIndex2 = key.LastIndexOf("_", startIndex1, StringComparison.Ordinal) + 1;
return key.Substring(startIndex2, startIndex1 - startIndex2);
}
public static KeyGenerator GetKeys(string sessionId, string applicationName)
{
return new KeyGenerator(sessionId, applicationName);
}
public static string GetExpirationIndexKey(string applicationName)
{
return applicationName + "_ExpirationIndex";
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment