عنوان:

‫از DAC تا cDAC؛ دات‌نت چگونه معماری Debugging داخلی CLR را بازطراحی می‌کند؟


نویسنده: امیر مکارچی
تاریخ: ۱۴۰۵/۰۵/۲۴ ۰۸:۱۰
آدرس: www.dntips.ir
دیباگر چگونه می‌فهمد Byteهای موجود در حافظه چه معنایی دارند. برای مثال، فرض کنید در یک Crash Dump آدرسی مانند زیر داریم:
0x000001F3A82C4010
این فقط یک عدد است.
از کجا باید بدانیم این آدرس به یک Managed Object اشاره می‌کند؟ اگر Object است، MethodTable آن کجاست؟ اگر به یک Thread داخلی CLR رسیده‌ایم، Offset مربوط به ThreadId کدام است؟ GC Heap از چند Segment تشکیل شده است؟ Pointer مربوط به Segment بعدی در کدام Offset قرار دارد؟ JIT Code Heapها چگونه به یکدیگر متصل شده‌اند؟ و مهم‌تر از همه، اگر ساختار داخلی CLR در نسخه بعدی .NET تغییر کرد، ابزار تشخیصی از کجا باید بفهمد Layout جدید چیست؟
سال‌ها پاسخ دات‌نت به این سؤال‌ها چیزی به نام DAC بوده است. اکنون این معماری در حال تغییر بنیادی است.
تیم Runtime دات‌نت در حال حرکت به سمت مدلی مبتنی بر Data Contract است؛ معماری‌ای که با نام cDAC شناخته می‌شود. به‌جای اینکه ابزار تشخیصی یک Binary دقیقاً منطبق با Runtime پیدا کند که «رازهای داخلی CLR» را از قبل بداند، خود Runtime بخشی از ساختار داخلی‌اش را در قالب یک Contract توصیف می‌کند و Reader می‌تواند آن را تفسیر کند. مستندات طراحی رسمی .NET نیز Data Contract را مکانیزمی برای مشاهده مستقیم وضعیت داخلی Runtime از طریق خواندن حافظه، هم در Live Debugging و هم در Post-mortem Debugging، تعریف می‌کنند.
این تغییر ممکن است در نگاه اول فقط یک Refactoring داخلی در Diagnostics دات‌نت به نظر برسد، اما در واقع یکی از مهم‌ترین تغییرات معماری در نحوه مشاهده CLR از بیرون است. برای درک اهمیت آن، ابتدا باید بفهمیم اصلاً چه مشکلی قرار است حل شود.

قبل از DAC: مسئله اصلی چیست؟
یک برنامه Managed روی CLR اجرا می‌شود. زمانی که برنامه‌ای مانند این را می‌نویسیم:
var customer = new Customer();
برای Developer، مفهوم Customer، Object، Field و Reference معنا دارد. اما در پایین‌ترین سطح، Process Memory چیزی جز Byte نیست. CLR باید مجموعه بزرگی از ساختارهای Native را برای مدیریت برنامه نگه‌داری کند:
MethodTable
MethodDesc
Thread
Module
Assembly
AppDomain
LoaderAllocator
LoaderHeap
GCHeap
HeapSegment
SyncBlock
CodeHeap
...
این ساختارها API عمومی دات‌نت نیستند. آن‌ها Implementation Detailهای Runtime هستند.
ممکن است Layout یک Structure در یک Build از Runtime چنین باشد:
Thread
+0x00 State
+0x10 Id
+0x20 Next
...
و در Build دیگری تغییر کند:
Thread
+0x00 State
+0x18 Id
+0x28 Next
...
Debugger نمی‌تواند صرفاً آدرس حافظه را بخواند و حدس بزند کدام Byte چه معنایی دارد. باید یک موجودیت وجود داشته باشد که دانش ساختارهای داخلی Runtime را داشته باشد. در معماری کلاسیک CLR، این وظیفه به DAC سپرده شد.

DAC چیست؟
DAC مخفف: Data Access Component است. در CoreCLR معمولاً آن را در قالب فایل زیر می‌بینیم:
mscordaccore.dll
یا معادل Platform-specific آن. وظیفه DAC این نیست که برنامه شما را Debug کند. وظیفه اصلی آن این است که به Debugger و ابزارهای Diagnostics کمک کند حافظه یک CLR را معنی‌دار تفسیر کنند. Issue اصلی cDAC در Repository رسمی dotnet/runtime نیز DAC را Componentای معرفی می‌کند که به Debuggerها و Diagnostic Toolها کمک می‌کند حافظه Process مربوط به .NET Runtime را بفهمند.
به زبان ساده می‌توان گفت:
Process Memory
      │
      ▼
     DAC
      │
      ▼
CLR concepts
Debugger می‌گوید: این آدرس را دارم. Threadهای CLR را به من بده.
DAC می‌داند: ThreadStore کجاست. Thread چه Layoutی دارد. Next Thread در چه Offsetی قرار گرفته است. State را از کجا باید خواند. و اطلاعات معنی‌دار را به Debugger برمی‌گرداند.

SOS در این معماری کجاست؟
اگر با Dump Debugging در .NET کار کرده باشید احتمالاً با SOS آشنا هستید. SOS یک Debugging Extension برای مشاهده وضعیت Managed Runtime است.
دستورهایی مانند:
!dumpheap
!clrstack
!dumpobj
!threads
!eeheap
!gcroot
از طریق SOS در دسترس هستند. طبق مستندات رسمی Microsoft، SOS می‌تواند اطلاعات مربوط به Managed Heap، Corruptionهای Heap، Typeهای داخلی Runtime و Managed Code را هم روی Live Process و هم روی Dump نمایش دهد. SOS همچنین همراه dotnet-dump و WinDbg در دسترس است. برای مثال وقتی می‌نویسید:
!dumpheap -stat
SOS باید بتواند بفهمد:
  • GC Heapها کجا هستند؛
  • Segmentها کجا قرار گرفته‌اند؛
  • Objectها چگونه Layout شده‌اند؛
  • MethodTable هر Object چیست.
اما SOS قرار نیست تمام Layout داخلی هر نسخه CLR را Hard-code کند. در معماری سنتی، بخش بزرگی از این دانش از طریق DAC در اختیار آن قرار می‌گیرد.
مدل ساده‌شده چیزی شبیه این است:
WinDbg / LLDB / dotnet-dump
           │
           ▼
          SOS
           │
           ▼
          DAC
           │
           ▼
   CLR Process / Dump
مستندات Microsoft برای Debugging Managed Code با WinDbg نیز صراحتاً SOS و Data Access Component را جزو اجزای موردنیاز برای مشاهده Managed Runtime معرفی می‌کنند.

ClrMD چه نقشی دارد؟
حالا فرض کنید نمی‌خواهید با دستورهای SOS کار کنید. می‌خواهید ابزار Diagnostics اختصاصی خودتان را با #C بسازید. اینجاست که ClrMD یا Microsoft.Diagnostics.Runtime وارد داستان می‌شود. ClrMD یک API مدیریت‌شده برای بررسی Live Processها و Crash Dumpهای .NET فراهم می‌کند. با آن می‌توان کدی شبیه این نوشت:
using DataTarget target =
    DataTarget.LoadDump("memory.dmp");

ClrRuntime runtime =
    target.ClrVersions[0].CreateRuntime();

foreach (ClrThread thread in runtime.Threads)
{
    Console.WriteLine(thread.ManagedThreadId);
}
از دید Developer فوق‌العاده ساده است. اما پشت این APIهای Friendly هنوز باید یک Component بداند Structure داخلی Thread در Runtime موردنظر چگونه است. ClrMD در معماری سنتی برای بخش مهمی از این اطلاعات به DAC متکی است. مستندات امنیتی خود ClrMD نیز توضیح می‌دهند که این Library فایل Native DAC را Load و Execute می‌کند و حتی به همین دلیل Signature Verification برای DAC در نظر گرفته شده است. این مدل سال‌ها جواب داده است. پس مشکل چیست؟

مشکل اول: DAC باید با Runtime تطابق داشته باشد
مهم‌ترین محدودیت معماری قدیمی این است که DAC نسبت به ران تایم Version-resilient نیست. DAC دانش Structureهای داخلی Runtime را هنگام Build درون خودش دارد. اگر Runtime تغییر کند، ممکن است:
Thread
MethodTable
MethodDesc
GCHeap
HeapSegment
LoaderHeap
هم تغییر کنند. بنابراین DAC مربوط به Runtime A الزاماً قادر نیست حافظه Runtime B را بفهمد. تیم .NET این مشکل را به‌صراحت در طراحی cDAC توضیح داده است: معماری سنتی CoreCLR نیاز دارد Debugger نسخه DAC و DBIای را پیدا و Load کند که با Runtime مورد Debug تطابق داشته باشد.
یعنی:
Runtime 8.0.x build A
        │
        └── DAC build A
نه:
Runtime 8.0.x build A
        │
        └── DAC build B
این موضوع مخصوصاً در Dump Debugging دردسرساز می‌شود.

سناریوی واقعی: Dump روی یک Server، تحلیل روی ماشین دیگر
تصور کنید Production روی Linux اجرا می‌شود. برنامه Crash می‌کند و شما Dump را می‌گیرید:
production.dmp
سپس Dump را به Workstation خود منتقل می‌کنید. حالا Tool تشخیصی باید Runtime دقیق موجود در Dump را تشخیص دهد و DAC سازگار با همان Runtime را پیدا کند. اگر Runtime مربوط به:
8.0.x
Linux
x64
specific servicing build
بوده باشد، DAC نیز باید اطلاعات سازگار با همان Target را داشته باشد. این همان چیزی است که در سال‌های گذشته باعث خطاهای آشنایی مانند:
Failed to load DAC
یا مشکلات مربوط به پیدا کردن mscordaccore شده است. حتی مستندات رسمی تحلیل Dump روی Linux هنوز هشدار می‌دهند که در معماری سنتی، Analysis ممکن است نیازمند ماشین با Architecture و Linux Distribution سازگار با محیط Capture باشد.

مشکل دوم: Cross-Architecture
فرض کنید Dump مربوط به Linux ARM64 است اما می‌خواهید آن را روی Windows x64 بررسی کنید. در معماری قدیمی، Host و Target Architecture وارد معادله DAC می‌شوند. Issue طراحی cDAC این محدودیت را یکی از انگیزه‌های اصلی معماری جدید معرفی کرده است؛ چراکه بعضی ترکیب‌های Host/Target برای DAC اصلاً قابل دسترس نیستند. در عمل نیز ClrMD در سال‌های گذشته با مشکلاتی مانند نبود DAC مناسب برای برخی Architectureها روبه‌رو بوده است؛ برای مثال Issue مربوط به Windows ARM64 دقیقاً به نبود DAC مناسب اشاره می‌کند.

مشکل سوم: Security
فرض کنید Runtime یک Build رسمی Microsoft نیست. ممکن است:
  • Runtime را خودتان Build کرده باشید؛
  • Vendor دیگری Runtime سفارشی داشته باشد؛
  • Runtime Patch شده باشد.
برای تحلیل آن باید DAC مرتبط را Load کنید. اما DAC یک Native Binary است. یعنی Debugger عملاً باید Code دیگری را داخل Process خودش Load و Execute کند. به همین دلیل مسئله Trust مطرح می‌شود. ClrMD امروزی به‌صورت پیش‌فرض DACهای Windows را از نظر Authenticode بررسی می‌کند و مستندات آن هشدار می‌دهند Disableکردن Signature Verification عمدتاً برای Runtimeهای Local-build شده مناسب است. تیم Runtime نیز Security را یکی از چهار مشکل اصلی معماری DAC/DBI قدیمی معرفی کرده است.

مشکل چهارم: Servicing
یک مشکل عمیق‌تر نیز وجود دارد. فرض کنید Bug در خود Runtime نیست. Bug در DAC است. یعنی Tool حافظه را اشتباه تفسیر می‌کند. در معماری قدیمی، اصلاح DAC به Runtime Build وابسته است. تیم .NET این مسئله را Servicing Problem می‌نامد: ارائه یک Fix صرفاً برای Debugger/DAC بدون انتشار Runtime Build جدید دشوار است. یعنی Diagnostics بیش از حد به Lifecycle خود Runtime گره خورده است.

مشکل بنیادی‌تر: Runtime خودش را توصیف نمی‌کند
ریشه تمام این مسائل را می‌توان در یک جمله خلاصه کرد:
در معماری DAC، دانش ساختار داخلی Runtime داخل یک Binary خارجی Build شده است.
به بیان دیگر:
Runtime memory
خودش نمی‌گوید:
Thread.Id is at offset 16
HeapSegment.Next is at offset 48
pointer size is 8
GC contract version is c1
این دانش در DAC قرار دارد. cDAC دقیقاً همین فرض بنیادی را تغییر می‌دهد.

ایده cDAC: Runtime خودش را معرفی کند
cDAC را می‌توان این‌طور خلاصه کرد:
به‌جای اینکه یک Binary خارجی ساختار Runtime را از قبل بداند، خود Runtime اطلاعات لازم برای تفسیر حافظه‌اش را منتشر کند.
این یعنی حرکت از Tool knows Runtime layout به سمت Runtime describes its layout این تغییر بسیار مهم است. در معماری جدید، Target می‌تواند بگوید:
PointerSize = 8

Type Thread:
    Id    -> offset 16
    OSId  -> offset 224
    State -> offset 0

GC Contract:
    version = c1
Reader دیگر لازم نیست Build دقیق Runtime را بشناسد. باید Contract را بفهمد. طراحی رسمی Data Contract می‌گوید هدف این معماری حذف نیاز به DAC و DBI دقیقاً منطبق و فراهم‌کردن روشی پایدار برای اینکه Tool خارجی بتواند رفتار Runtime را مشاهده و درک کند است.

حرف c در cDAC به چه معناست؟
نام cDAC ممکن است کمی گمراه‌کننده باشد. در Glossary رسمی Repository دات‌نت، CDAC به‌عنوان Codename مربوط به Data Contracts معرفی شده است.
در عمل می‌توان آن را contract-based DAC در نظر گرفت. هدف آن ارائه همان نوع قابلیت Diagnostics است، اما بر مبنای Contractهای صریح و Versioned، نه دانش Hard-coded وابسته به یک Runtime Build.

Data Contract دقیقاً چیست؟
برای فهم cDAC باید بین دو مفهوم تفاوت بگذاریم Data Descriptor و Algorithmic Contract این تفکیک قلب معماری جدید است.

بخش اول: Data Descriptor
Data Descriptor پاسخ می‌دهد:
داده‌های Runtime در حافظه چه شکلی هستند؟
مثلاً:
Thread.Id offset = 16
Thread.OSId offset = 224
HeapSegment.Next offset = 80
یا:
NumHeaps global exists
یا:
GC contract version = c1
Data Descriptor شامل چیزهایی مانند:
  • Typeها؛
  • Field Offsetها؛
  • Size بعضی Typeها؛
  • Global Valueها؛
  • Sub-descriptorها؛
  • Version Contractها
است. طراحی رسمی نیز Data Descriptor را موجودیتی می‌داند که Layout Typeهای موردنیاز Contractها و Global Valueهای Runtime Target را تعریف می‌کند.

بخش دوم: Algorithmic Contract
اما دانستن Offset کافی نیست. فرض کنید Descriptor به ما بگوید:
LoaderHeap.FirstBlock offset = 32
LoaderHeapBlock.Next offset = 24
هنوز باید بدانیم چگونه Loader Heap را Enumerate کنیم. Algorithm ممکن است بگوید:
1. FirstBlock را بخوان.
2. VirtualAddress و VirtualSize را استخراج کن.
3. Next را بخوان.
4. تا Null شدن Next ادامه بده.
این همان Algorithmic Contract است. در مستندات رسمی، Algorithmic Contract به‌عنوان الگوریتمی تعریف شده که با استفاده از Data Structureها و Global Valueهای Descriptor، اطلاعات مفید درباره Runtime تولید می‌کند.
پس:
Data Descriptor
      =
What does memory look like?

Algorithmic Contract
      =
How should I interpret it?
این تفکیک، پایه معماری cDAC است.

Runtime چگونه Descriptor را در اختیار Tool می‌گذارد؟
همه‌چیز از یک Symbol صادرشده شروع می‌شود:
DotNetRuntimeContractDescriptor
این Symbol به Struct کوچکی اشاره می‌کند:
struct DotNetRuntimeContractDescriptor
{
    uint64_t  magic;
    uint32_t  flags;
    uint32_t  descriptor_size;
    const char *descriptor;
    uint32_t  pointer_data_count;
    uint32_t  pad0;
    uintptr_t *pointer_data;
};
Layout رسمی همین Structure در Specification مربوط به Contract Descriptor تعریف شده است. این Structure عملاً Bootstrap Point کل سیستم است. Reader ابتدا همین Symbol را پیدا می‌کند و سپس از روی آن بقیه اطلاعات Runtime را کشف می‌کند.

Descriptor در واقع JSON است
یکی از جذاب‌ترین قسمت‌های طراحی این است که بخش عمده Metadata در قالب JSON بیان می‌شود. نمونه ساده‌شده:
{
  "version": "1",

  "types": {
    "Thread": {
      "Id": 16,
      "OSId": 224,
      "State": 0
    }
  },

  "globals": {
    "GCInfoVersion": "0x4",
    "AppDomain": [[2], "pointer"]
  },

  "sub-descriptors": {
    "GC": [[45], "pointer"]
  },

  "contracts": {
    "GC": "c1",
    "Thread": "c1",
    "SyncBlock": "c1"
  }
}


چرا Pointerهای واقعی داخل JSON نیستند؟
JSON هنگام Build ساخته می‌شود. اما آدرس واقعی چیزهایی مانند:
AppDomain
GC heap table
ThreadStore
در Runtime مشخص می‌شود. بنابراین نمی‌توان آدرس واقعی آن‌ها را مستقیماً در JSON ثابت قرار داد. راه‌حل cDAC یک Array جانبی است:
pointer_data
JSON به‌جای Address واقعی می‌گوید:
"AppDomain": [[2], "pointer"]
Reader می‌فهمد باید مقدار Slot شماره دو از pointer_data را بخواند. به این ترتیب:
Static metadata
از:
Runtime addresses
جدا می‌شود. Contract Descriptor رسمی نیز pointer_data را Auxiliary Data مربوط به JSON Descriptor تعریف می‌کند.

آیا با JSON همه‌چیز حل شد؟
خیر. این نکته مهم است. Descriptor فقط می‌گوید داده چگونه Layout شده است. مثلاً:
HeapSegment.Next -> offset X
اما نمی‌گوید چگونه کل GC Heap را Enumerate کنیم. Algorithmها در Repository خود dotnet/runtime مستند می‌شوند. ساختار کلی به این صورت است:
docs/design/datacontracts/
و Contractهایی مانند:
GC.md
Loader.md
ExecutionManager.md
Thread.md
...
در آن قرار دارند. طراحی رسمی الزام می‌کند Specification هر Contract در فایل مستقل خود و Versionهای مختلف همان Contract در همان فایل مستند شوند. هم‌زمان Microsoft یک Reader واقعی #C نیز در:
src/native/managed/cdac
توسعه می‌دهد. Repository رسمی امروز پروژه‌های مربوط به Microsoft.Diagnostics.DataContractReader را در همین مسیر نگه‌داری می‌کند.

این تفاوت با DAC چرا مهم است؟
در معماری قدیمی:
Runtime version
       +
Architecture
       +
Platform
       ↓
matching DAC
بخش مهمی از امکان Analysis را تعیین می‌کرد. در معماری Contract محور، هدف نهایی بسیار نزدیک‌تر به این است:
Memory Reader
      +
Contract Descriptor
      +
Algorithm implementation
      ↓
Runtime analysis
این یعنی Coupling از exact Runtime binary به documented Contract منتقل می‌شود. Issue اصلی cDAC هدف بلندمدت را Libraryای توصیف می‌کند که بتواند روی Runtimeهای متعدد از CoreCLR از .NET 9 به بعد و در آینده حتی Runtimeهایی مانند NativeAOT و Mono کار کند. این هدف هنوز به‌طور کامل تحقق نیافته، اما جهت معماری روشن است.

Versioning؛ بخش کلیدی معماری
اگر قرار است cDAC مشکل Version Lock را حل کند، طبیعتاً باید مدل Versioning خوبی داشته باشد. اما Versioning در cDAC کمی متفاوت از چیزی است که معمولاً تصور می‌کنیم. Descriptor ممکن است بگوید:
"GC": "c1"
یا Contract دیگری Version متفاوتی داشته باشد.
نکته مهم:
Version بزرگ‌تر لزوماً Version جدیدتر یا بهتر نیست.
طراحی رسمی صراحتاً می‌گوید Contract Version Identifier باید مانند یک شناسه Opaque در نظر گرفته شود؛ مقدار «بزرگ‌تر» الزاماً جدیدتر نیست.
مثلاً ممکن است:
GC c1
یک Algorithm داشته باشد و:
GC c2
Algorithm دیگری. Reader باید Dispatch کند:
IGC gc = version switch
{
    "c1" => new GCContractV1(target),
    "c2" => new GCContractV2(target),

    _ => throw new NotSupportedException()
};

آیا این یعنی DAC همین امروز حذف شده است؟
خیر. و این یکی از نکاتی است که هنگام صحبت درباره cDAC باید با دقت بیان شود. cDAC یک مهاجرت تدریجی است، نه Switch یک‌شبه. برنامه اولیه تیم Runtime این بود که DAC موجود بتواند بعضی Operationها را به cDAC Delegate کند. یکی از Milestoneهای اولیه نیز پیاده‌سازی !PrintException روی cDAC بود. سپس دامنه APIهای موردنیاز SOS و CLRMA به‌تدریج گسترش یافت. این یعنی مدتی معماری Hybrid خواهیم داشت:
Diagnostic Tool
       │
       ▼
Legacy DAC surface
       │
       ├── old implementation
       │
       └── cDAC-backed implementation
هدف این است که با افزایش Coverage، وابستگی به DAC سنتی کمتر و کمتر شود.

وضعیت cDAC در ۲۰۲۶
تا اوت ۲۰۲۶، cDAC دیگر یک Experiment ابتدایی نیست. Repository رسمی dotnet/runtime مجموعه بزرگی از Contractها، Managed Reader و تست‌های Cross-platform برای آن دارد و فعالیت توسعه cDAC همچنان بسیار بالاست. حتی Pull Requestهای روز ۱۴ اوت ۲۰۲۶ همچنان تغییرات جدید cDAC را شامل می‌شوند. برای .NET 11، کار مشخصی روی Deployکردن cDAC همراه SOS و CLRMA انجام شده است. Tracking مربوط به APIهای SOS نشان می‌دهد بخش هدف‌گذاری‌شده آن برای Milestone .NET 11 تکمیل شده، در حالی که Trackingهای دیگری برای DacDbi و سایر قابلیت‌ها همچنان فعال هستند.
بنابراین بهتر است وضعیت را این‌طور توصیف کنیم:
.NET 9: زیرساخت‌ها و backportهای اولیه Contract
.NET 10: گسترش Reader و APIها و مسیر Delegation
.NET 11: ادغام بسیار جدی‌تر با SOS / CLRMA / Debugging stack
بعد از آن: کاهش بیشتر Legacy DAC dependency
+ NativeAOT
+ Mono
+ public contract-based diagnostics API
این Timeline خلاصه‌ای مفهومی از Tracking رسمی پروژه است و نباید به معنای Completeبودن تمام قابلیت‌ها در هر Version تلقی شود. Epic کلی Portable Data Contract-based DAC اکنون کارهای Future را نیز دنبال می‌کند و Milestone آن روی .NET 12 قرار گرفته است.

چرا cDAC برای Dump Debugging مهم است؟
بزرگ‌ترین دستاورد بالقوه cDAC شاید برای Developer عادی هنگام Debug کردن Production Dump دیده شود. مدل ایده‌آل آینده:
Dump
 │
 ├── Runtime memory
 │
 └── Runtime contract descriptor
          │
          ▼
   portable managed reader
است. Tool مجبور نیست برای هر Build:
find exact DAC
download exact DAC
load native DAC
trust native DAC
match architecture
را تکرار کند. به‌جای آن Runtime در Snapshot خودش اطلاعاتی دارد که Reader برای فهم Layout به آن نیاز دارد. این همان تغییری است که می‌تواند Dump Analysis را از یک مدل:
binary-match driven
به:
contract-driven
تبدیل کند.

چرا cDAC برای ابزارهای Observability هم مهم است؟
Data Contract صرفاً برای WinDbg ساخته نشده است. Specification رسمی صراحتاً Debugger، Post-mortem Debugging، Profiler و سایر Diagnostic Toolها را از مصرف‌کنندگان این Contractها می‌داند و حتی سناریوهایی مانند Unwinding کد JITشده از طریق eBPF را به‌عنوان کاربرد بالقوه مطرح می‌کند. یعنی در آینده Toolهای بیشتری می‌توانند بدون Private Knowledge عمیق از Build خاص CLR، Runtime Internals را مشاهده کنند.
این موضوع می‌تواند روی:
profilers
production diagnostics
memory analyzers
crash analyzers
runtime inspection tools
eBPF-based observability
اثر بگذارد.

چرا cDAC برای تیم Runtime هم بهتر است؟
در معماری قبلی، تغییر Structure داخلی ممکن بود اثرهای زنجیره‌ای پیچیده‌ای روی DAC داشته باشد. در مدل Contract، Runtime باید آن چیزی را که Diagnostic Consumerها مجاز به تکیه‌کردن بر آن هستند، Explicitly منتشر کند. این موضوع مرز بهتری ایجاد می‌کند:
Runtime private implementation
        │
        ├── completely private details
        │
        └── diagnostic contract surface
اما هزینه هم دارد. تیم Runtime حالا باید Contractها، Type Definitionها، Versionها و Reader Implementationها را روی Buildها و Architectureهای مختلف با دقت Validate کند.

cDAC چه چیزی نیست؟
برای جلوگیری از چند سوءبرداشت، باید روشن کنیم cDAC:
جایگزین GC نیست هیچ تغییری در نحوه مدیریت حافظه برنامه ایجاد نمی‌کند.
جایگزین JIT نیست کد را Compile نمی‌کند.
جایگزین SOS به معنای مستقیم نیست SOS می‌تواند Consumer قابلیت‌های cDAC باشد.
یک Serialization عمومی از کل CLR نیست Runtime تمام Implementation Detailهای خود را منتشر نمی‌کند؛ تنها Surface لازم برای Contractهای تشخیصی را منتشر می‌کند.
تضمین نمی‌کند هر Dump خراب قابل تحلیل باشد اگر Memory Capture ناقص یا Corrupt باشد، Reader همچنان باید Defensive Programming داشته باشد.


این تغییر برای یک Developer معمولی چه معنایی دارد؟
ممکن است هیچ‌وقت DotNetRuntimeContractDescriptor را مستقیماً نخوانید. ممکن است هیچ‌وقت Contract GC.md را Implementation نکنید. اما نتیجه این معماری را احتمالاً در Toolهایی که روزانه استفاده می‌کنید خواهید دید. در بلندمدت انتظار می‌رود مواردی مانند:
dotnet-dump
dotnet-debug
SOS
ClrMD
WinDbg integrations
cross-platform diagnostics
بتوانند وابستگی کمتری به DAC دقیق Runtime داشته باشند. خود dotnet-dump امروز Tool رسمی برای Capture و Analysis Dump روی Windows، Linux و macOS است و SOS Commandها را برای بررسی Crash و GC ارائه می‌کند. در کنار آن، dotnet-debug نیز در نسخه‌های جدید برای Attach به Live Process و Analysis تعاملی Dump عرضه شده و همان مجموعه SOS Commandها را ارائه می‌کند. cDAC در لایه پایین‌تر می‌تواند زیرساختی باشد که این Toolها را در آینده Portableتر و Runtime-version-resilientتر می‌کند.