如何抛出反序列化异常?

How to throw a deserialized exception?

我正在使用 JsonConvert.SerializeObject 在服务器上序列化 Exception,然后编码为 byte[] 并在客户端使用 JsonConvert.DeserializeObject 进行反序列化。到目前为止一切正常...问题是当我抛出 Exception 堆栈跟踪被替换时,让我演示一下:

public void HandleException(RpcException exp)
{
    // Get the exception byte[]
    string exceptionString = exp.Trailer.GetBytes("exception-bin");
    
    // Deserialize the exception
    Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new 
    JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
    
    // Log the Exception: The stacktrace is correct. Ex.: at ServerMethod()
    Console.WriteLine(exception);
    
    // Throw the same Exception: The stacktrace is changed. Ex.: at HandleException()
    ExceptionDispatchInfo.Capture(exception).Throw();
}

如果反序列化 Exception 并设置 JsonSerializerSettings.Context = new StreamingContext(StreamingContextStates.CrossAppDomain) then the deserialized stack trace string will get prepended to the displayed StackTrace 即使在抛出异常之后:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    Context = new StreamingContext(StreamingContextStates.CrossAppDomain),
};
var exception = JsonConvert.DeserializeObject<Exception>(exceptionString, settings);

备注:

  • 这是有效的,因为在 streaming constructor for Exception 中,反序列化的堆栈跟踪字符串被保存到 _remoteStackTraceString 中,稍后将其添加到常规堆栈跟踪中:

    if (context.State == StreamingContextStates.CrossAppDomain)
    {
        // ...this new exception may get thrown.  It is logically a re-throw, but 
        //  physically a brand-new exception.  Since the stack trace is cleared 
        //  on a new exception, the "_remoteStackTraceString" is provided to 
        //  effectively import a stack trace from a "remote" exception.  So,
        //  move the _stackTraceString into the _remoteStackTraceString.  Note
        //  that if there is an existing _remoteStackTraceString, it will be 
        //  preserved at the head of the new string, so everything works as 
        //  expected.
        // Even if this exception is NOT thrown, things will still work as expected
        //  because the StackTrace property returns the concatenation of the
        //  _remoteStackTraceString and the _stackTraceString.
        _remoteStackTraceString = _remoteStackTraceString + _stackTraceString;
        _stackTraceString = null;
    }
    
  • 虽然 Exception 的序列化流确实包含堆栈跟踪字符串,但它不会尝试捕获 private Object _stackTrace which is used by the runtime to identify where in the executing assembly the exception was thrown. This would seem to be why ExceptionDispatchInfo 在抛出时无法复制和使用此信息例外。因此,似乎不可能抛出反序列化异常并从序列化流中恢复其“真实”堆栈跟踪。

  • 为了 Json.NET 使用其流式构造函数反序列化一个类型(从而根据需要设置远程跟踪字符串),该类型必须标记为 [Serializable] and implement ISerializable. System.Exception meets both requirements, but some derived classes of Exception do not always add the [Serializable] attribute. If your specific serialized exception lacks the attribute, see .

  • 使用 TypeNameHandling.All 反序列化异常是不安全的,并且在从不受信任的来源反序列化时可能会导致注入攻击类型。请参阅: 其答案专门讨论了异常的反序列化。

演示 fiddle here.

我想指出的只是一个小案例:我从两个应用程序调用此 seralization/deserialization,一个是 Blazor (.net 5),另一个是 WinForms (.net framework 4.7)。在 blazor 中,接受答案的方法不起作用。我在这种情况下所做的是通过反射设置 RemoteStackTrace。

// Convert string para exception
Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

// Set RemoteStackTrace
exception.GetType().GetField("_remoteStackTraceString", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, exception.StackTrace);

// Throw the Exception with original stacktrace
ExceptionDispatchInfo.Capture(exception).Throw();