簡體   English   中英

以 DateTime 為目標的調試可視化工具不會傳輸值

[英]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

這可能是什么原因造成的? 我該如何解決?


一些隨意的筆記

  • 這不是因為調試器端總是 32 位,而被調試者端有時是 64 位。
  • 也不是因為不同的 TFM - 當調試器端針對 .NET 框架時,而被調試端可以針對 .NET 標准或 .NET 核心。
  • 只有TransferData覆蓋受到影響; GetData覆蓋總是得到目標值(我實際上是使用GetData來解決這個問題,但我真的更願意使用GetData來做其他事情。)我試圖測試ReplaceData / ReplaceObject ,但IsObjectReplaceable屬性總是返回false
  • 我已經針對其他值類型進行了測試—— TimeSpanDateTimeOffset和自定義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的情況下不是問題,所以只是附帶說明:在這種情況下,技巧可以是將自定義可序列化數據放入實現IObjectReferenceReplaceObject中,以便您可以在 IObjectReference 中返回自定義構建的結果IObjectReference.GetRealObject 是我的序列化程序中的一個這樣的例子。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM