[英]Why doesn't the VS 2015 debug visualizer work on this unsafe struct?
[英]Debugging visualizer which targets DateTime doesn't transfer the value
我编写了一个针对DateTime
( repo ) 的Visual Studio 调试可视化工具。 我的问题是,如果目标表达式是object
而不是DateTime
(问题),则调试器端仅将目标值传递给被调试者端。
我已经发布了一个 GH repo,其中包含一个重现问题的 MCVE 。 调试器端如下所示:
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) {
var response = objectProvider.TransferObject(5);
var msg = response switch {
string s => s,
IEnumerable e => string.Join(", ", e.Cast<object>()),
_ => "Unhandled type"
};
MessageBox.Show(msg);
}
和被调试方看起来像这样:
public override void TransferData(object target, Stream incomingData, Stream outgoingData) {
int? repetitions = Deserialize(incomingData) switch {
int i when i > 0 => i,
string s when int.TryParse(s, out int i) && i > 0 => i,
_ => null
};
object toSerialize =
repetitions is null ? $"Invalid value for repetitions" :
target switch {
DateTime dt => Repeat(dt, repetitions.Value).ToArray(),
null => $"{nameof(target)} is null",
_ => $"Not implemented for target of type {target.GetType().FullName}" as object
};
Serialize(outgoingData, toSerialize);
}
在我构建并安装可视化工具后,开始调试以下代码:
var dte = DateTime.UtcNow;
object o = dte;
如果我在o
上 hover 并触发可视化器,则目标DateTime
被传递到被调试者端,并返回DateTime
数组。 但是,如果我在dte
上触发可视化器,我会返回字符串target is null
,这意味着调试端已在target
参数中收到null
。
这可能是什么原因造成的? 我该如何解决?
TransferData
覆盖受到影响; GetData
覆盖总是得到目标值(我实际上是使用GetData
来解决这个问题,但我真的更愿意使用GetData
来做其他事情。)我试图测试ReplaceData
/ ReplaceObject
,但IsObjectReplaceable
属性总是返回false
。TimeSpan
、 DateTimeOffset
和自定义struct
——并且看到了相同的行为。 但是,当我针对int
进行测试时,目标进程崩溃并且调试 session 被中断。DateTime?
显示与DateTime
相同的行为; 我想这是因为它们都以相同的方式序列化。int
为目标时的异常命中的堆栈跟踪针对此评论,可视化int
时的错误消息如下:
目标进程在评估 function 'Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData'时以代码 -1073740791 (0xC0000409) 退出。
如果问题经常发生,请考虑禁用工具->选项设置“调试->常规->启用属性评估和其他隐式 function 调用”或通过评估即时 window 中的表达式来调试原因。 有关执行此操作的信息,请参阅帮助。
随后是另一条消息:
无法加载自定义查看器。
目标进程崩溃,调试 session 结束。
我尝试使用代码断点( Debugger.Break()
)附加调试器,但未成功。 如果我从可视化器( new System.Diagnostics.StackTrace().ToString()
)返回调用堆栈并且可视化器成功运行,我会得到以下信息:
在 SimpleValueTypeVisualizer.Debuggee.VisualizerObjectSource.TransferData(对象目标,Stream 传入数据,Stream 传出数据)
在 Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData(对象可视化对象,字节 [] uiSideData)
在 TestNoRef.Program.Main(String[] args)
这似乎意味着Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData
有一些例外。
当我使用 ILSpy 打开DebuggerVisualizers.dll
时,相关的TransferData
方法如下所示:
// Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost
using System.IO;
public byte[] TransferData(object visualizedObject, byte[] uiSideData)
{
MemoryStream memoryStream = new MemoryStream();
MemoryStream incomingData = ((uiSideData != null) ? new MemoryStream(uiSideData) : null);
m_debuggeeSideVisualizerObject.TransferData(visualizedObject, incomingData, memoryStream);
return memoryStream.ToArray();
}
我猜想例外是在方法的第三行( MemoryStream incomingData =...
)。 但是我仍然不清楚异常的细节,特别是为什么问题只出现在未装箱的值上,而不是装箱的值上。
根据此评论,我包含了在打开可视化工具时创建的事件日志中的数据,该表达式为int
类型:
Log Name: Application
Source: Application Error
Date: 22/04/2021 12:14:36
Event ID: 1000
Task Category: (100)
Level: Error
Keywords: Classic
User: N/A
Computer: LAPTOP-7O43T4OO
Description:
Faulting application name: TestNoRef.exe, version: 1.0.0.0, time stamp: 0xd9f9e12d
Faulting module name: clr.dll, version: 4.8.4341.0, time stamp: 0x6023024f
Exception code: 0xc0000409
Fault offset: 0x00574845
Faulting process ID: 0x94c4
Faulting application start time: 0x01d73757c33e87c0
Faulting application path: ***********
Faulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Report ID: 1dcf070b-71ff-4279-be71-822698cc6168
Faulting package full name:
Faulting package-relative application ID:
Event Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Application Error" />
<EventID Qualifiers="0">1000</EventID>
<Version>0</Version>
<Level>2</Level>
<Task>100</Task>
<Opcode>0</Opcode>
<Keywords>0x80000000000000</Keywords>
<TimeCreated SystemTime="2021-04-22T09:14:36.4507272Z" />
<EventRecordID>1180760705</EventRecordID>
<Correlation />
<Execution ProcessID="0" ThreadID="0" />
<Channel>Application</Channel>
<Computer>LAPTOP-7O43T4OO</Computer>
<Security />
</System>
<EventData>
<Data>TestNoRef.exe</Data>
<Data>1.0.0.0</Data>
<Data>d9f9e12d</Data>
<Data>clr.dll</Data>
<Data>4.8.4341.0</Data>
<Data>6023024f</Data>
<Data>c0000409</Data>
<Data>00574845</Data>
<Data>94c4</Data>
<Data>01d73757c33e87c0</Data>
<Data>***********</Data>
<Data>C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll</Data>
<Data>1dcf070b-71ff-4279-be71-822698cc6168</Data>
<Data>
</Data>
<Data>
</Data>
</EventData>
</Event>
我找不到合适的解决方案。 这可能只是最新版本之一中引入的错误,到目前为止,没有人遇到过值类型的这个问题。 其实我试过
DateTime dte = DateTime.UtcNow;
ValueType vt = dte;
同样,它确实适用于vt
但不适用于dte
。 为了以防万一,我向 net48 添加了一个明确的目标,但它没有任何改变。
我能想到的最好的解决方法是与我猜测 Zev Spitz 正在使用的解决方法非常相似,但尽量不要浪费 GetData 覆盖只是为了获得目标值。 恐怕这不是一个很好的解决方案。
如果您想使用 GetData 检索不同的值,但它将在您的 DialogDebuggerVisualizer.Show 覆盖中使用,您可以在调用 GetData 时将值存储在 VisualizerObjectSource object 中,并在调用 TransferData 时检索它,而无需实际传输它从 Debuggee 到 Debugger。
public class VisualizerObjectSource : Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource
{
/*static*/ DateTime? _lastDatetime=null;
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
target = _lastDatetime;
//Calculate here the output value
object toSerialize = " is null = " + (target==null).ToString();
Serialize(outgoingData, toSerialize);
}
public override void GetData(object target, Stream outgoingData)
{
_lastDatetime = (DateTime)target;
//Calculate here what you want to be returned by GetData
base.GetData(" The stuff you want to return ", outgoingData);
}
}
并在您的调试器端,确保在调用 TransferObject() 之前调用 GetObject/GetData
protected override void Show(IDialogVisualizerService windowService,
IVisualizerObjectProvider objectProvider)
{
object MyCustomStuff =objectProvider.GetObject();
var response = objectProvider.TransferObject(5);
//[...]
string msg = response .ToString();
MessageBox.Show(msg);
}
正如我在评论中提到的,实际上不需要TransferData
。 此外,可以避免整个BinaryFormatter
序列化(不幸的是,仅在将数据传输到调试器可视化器时,而不是在替换编辑值时,但还有另一个技巧)。
首先,考虑以下设置:
[assembly: DebuggerVisualizer(typeof(DateTimeVisualizer), typeof(DateTimeSerializer),
Target = typeof(DateTime),
Description = "DateTime Debugger Visualizer")]
其中序列化器如下:
// Note that TransferData is not overridden, and we do not call base.GetData so we can
// avoid using BinaryFormatter (which often has issues when debugging a .NET Core or newer project)
internal class DateTimeSerializer : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var dateTime = (DateTime)target;
// Note: do not dispose the writer so the outgoingData remains open.
// If targeting newer frameworks you can use the leaveOpen parameter, too.
var writer = new BinaryWriter(outgoingData);
// What a tiny payload compared to the default BinaryFormatter result...
writer.Write(dateTime.Ticks);
writer.Write((int)dateTime.Kind);
}
}
在可视化器本身中,重要的是不要调用GetObject
,这会尝试通过BinaryFormatter
反序列化您的 stream 。 相反,使用GetData
,它返回原始 stream:
internal class DateTimeDebuggerVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
// GetObject would fail here as we have a custom written stream
var reader = new BinaryReader(objectProvider.GetData());
var dateTime = new DateTime(reader.ReadInt64(), (DateTimeKind)reader.ReadInt32());
// Show the debugger [...]
}
如果您的调试器可以编辑数据,那么替换值是另一个问题。 不幸的是,在这种情况下无法避免BinaryFormatter
序列化。 如果您的 object 不能在每个目标中序列化(例如,在 .NET Core 及更高版本中),则可能会出现问题。
当然,这在DateTime
的情况下不是问题,所以只是附带说明:在这种情况下,技巧可以是将自定义可序列化数据放入实现IObjectReference
的ReplaceObject
中,以便您可以在 IObjectReference 中返回自定义构建的结果IObjectReference.GetRealObject
。 这是我的序列化程序中的一个这样的例子。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.