{"id":707,"date":"2009-10-14T00:00:00","date_gmt":"2009-10-14T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/net-debugging-dont-give-me-none-of-your-vs\/"},"modified":"2026-03-06T13:21:31","modified_gmt":"2026-03-06T13:21:31","slug":"net-debugging-dont-give-me-none-of-your-vs","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/net-debugging-dont-give-me-none-of-your-vs\/","title":{"rendered":".NET Debugging with WinDbg, CDB, and SOS.dll"},"content":{"rendered":"\n<div id=\"PRETTY\">\n<p>When Visual Studio isn\u2019t available &#8211; on a production server, analyzing a crash dump, or debugging a process remotely &#8211; you can use Microsoft\u2019s free debugging tools: WinDbg (graphical debugger), CDB (command-line debugger), and SOS.dll (a .NET-specific extension that understands managed code). These tools let you attach to running processes, set breakpoints on module loads, inspect the managed heap, view CLR call stacks, and analyze exceptions. This guide walks through the debugging landscape from MSIL compilation to machine code, then demonstrates a practical debugging session using CDB and SOS to diagnose a caught exception.<\/p>\n<h2>Introduction<\/h2>\n<p class=\"MsoNormal\">There will come a time that any developer will need to debug an application and will not have access to Visual Studio or, in some cases, even the source code. When debugging a problem on a production web or application server, for example, I really do not want to have to install Visual Studio and copy across all my source code; it just isn&#8217;t practical, or sometimes even allowed.\u00a0It is at times like this that we need another tool, a tool for debugging windows applications, and it just so happens that Microsoft provides a range of such <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/debugger\/\">debuggers<\/a>, which are ideally suited to this type of situation. In this article I am going to explain what debuggers are available, and how you can use them to simplify the process of debugging a .NET application in situations where Visual Studio isn&#8217;t either practical or available.<\/p>\n<p>In this article I am going to explain what debuggers are available, and how to debug a simple,\u00a0fairly common example.\u00a0 I hope this will show how it is possible to debug your code in a simple straightforward way.<\/p>\n<h2>Why bother?<\/h2>\n<p class=\"MsoNormal\">On the whole, if you are working at your developer machine and are able to reproduce the reported issue, then it is easiest to do your debugging in Visual Studio. However, as I hinted in the introduction, there are many reasons why you cannot always use Visual Studio, and why you should learn and understand the alternatives:<\/p>\n<ul>\n<li><b>Visual Studio Crashes<\/b> &#8211; while not a common occurrence, every developer knows that VS sometimes crashes, and often when you need it most. WinDBG \/ cdb do occasionally crash but rarely and if they do have issues it is pretty simple to download an older or newer version that should be fine..<\/li>\n<li><b>Speed<\/b> &#8211; if you are in a hurry and you just want to see something quickly, then starting up <b>cdb<\/b> takes a fraction of the time it takes to start Visual Studio, and has a significantly smaller footprint.<\/li>\n<li><b>Control<\/b> &#8211; the debugging tools provide a wide range of commands and options that allow fine-grained control of the debugging process. For example, you can set a break point on a particular module being loaded, or make changes to the application&#8217;s data and code (i.e. you can apply a one- time only patch to your application at run time!).<\/li>\n<li><b>Free<\/b> &#8211; the debuggers and SOS are free, you can download them from the Microsoft site and they are updated fairly regularly. As such they make very useful alternatives when Visual Studio is unavailable.<\/li>\n<li><b>Crash Dumps<\/b> &#8211; it is simple to create and to view crash dumps, so you can get a dump from a customer or only interrupt service on your live applications briefly while you take a snapshot of it for subsequent debugging. You can debug dumps taken from any of the common tools like DrWatson, ADPlus and Debug Diag .<\/li>\n<li><b>SOS includes various helper functions<\/b> &#8211; these allow you to debug .NET deadlocks and list all the objects in memory whilst being able to find what created them to track down memory leaks.<\/li>\n<li><b>Remote debugging is significantly simpler<\/b> &#8211;\u00a0 just install on the server and client the <b>remote.exe<\/b> that comes with the tools and you are ready.<\/li>\n<li><b>X64 support <\/b>&#8211; there are no problems debugging X64 applications using the debugging tools, whereas there are issues using Visual Studio to do this.<\/li>\n<\/ul>\n<h2>Meet the debuggers<\/h2>\n<p class=\"MsoNormal\">There are two types of debuggers: <b>kernel<\/b> and <b>user-mode<\/b>.\u00a0Kernel debuggers are used to debug drivers and the Windows kernel. User-Mode debuggers are used for applications and services.\u00a0We are interested in user-Mode debuggers of which there are two in the Windows debugging toolkit: <b>WinDbg<\/b>, which is GUI\u00a0based (with a circa 1990&#8217;s interface) and <b>cdb<\/b>, which is a command line tool.<\/p>\n<p class=\"MsoNormal\">Both of these debuggers provide a wrapper around <b>dbgEng.dll<\/b>, which actually does the debugging.\u00a0The commands and responses are the same from all debuggers so just choose which tool you like and stick to that. I prefer <b>cdb<\/b> because I like the fact that it only gives you a prompt back when it is ready to accept input. WinDbg, on the other hand, offers such things as stack and variable windows and lets you happily type away before it is ready. The debuggers are assembly debuggers. They let you control the process you are investigating, set break points, and view threads and variables in assembly code. This\u00a0means that you need to debug and understand machine code, \u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Calling_convention\">calling conventions<\/a>, stacks, heaps and memory and so on. The debuggers provide the symbols from your source code in the assembly language, which allow you to get line locations and view different structures and classes but it is still fairly complicated. Even when you have symbols, you have to ensure they are compiled with the executable otherwise they will not match and will give you strange results.<\/p>\n<p class=\"MsoNormal\">Luckily this nightmare can be averted to an extent because some nice people in Microsoft decided to help the developer community and ship a helper DLL with the .NET framework, <b>sos.dll<\/b>. It is amazing how appropriate that name really is. This DLL can be loaded by any of the debuggers mentioned above, and understands how the CLR works. All .NET programs provide a wealth of information at run time which we can take advantage of, at least compared with native applications, so .NET debugging really is very simple.<\/p>\n<p class=\"MsoNormal\">Taking the time now to understand a few simple procedures really will pay dividends and, if you are anything like me, you will think that debugging without VS really is pretty cool and helps you gain a thorough understanding of how the .NET framework actually works.<\/p>\n<h2>The Debugging Landscape<\/h2>\n<p>Before we delve into how to actually debug code, it is important to understand what happens when you run a .NET application:<\/p>\n<ol>\n<li>.NETcode is written in a number of languages: C#, VB.NET or any other CLI-compliant language.<\/li>\n<li>This code is compiled into a common MSIL format<\/li>\n<li>At runtime, or if NGEN&#8217;d,\u00a0 MSIL is compiled to binary instructions for the CPU architecture that it is running on (JITed) , and then executed<\/li>\n<\/ol>\n<p>By way of an example, let&#8217;s take a look at some C# code that performs some looping and then outputs some text, and see how the code looks as it is transformed into MSIL then into machine code. I have highlighted the areas which match in each format.<\/p>\n<ol>\n<li>C# Code\n<pre class=\"lang:c# theme:vs2012\">static void DoSomething()  \n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for (int i = 0; i &lt; 10; i++)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Console.WriteLine(String.Format(\"Number: {0}\", i));\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n<\/pre>\n<\/li>\n<li>MSIL Version:\n<pre class=\"lang:c# theme:vs2012\">.method private hidebysig static void\u00a0 DoSomething() cil managed  \n{\n\u00a0 \/\/ Code size\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 43 (0x2b)\n\u00a0 .maxstack\u00a0 2\n\u00a0 .locals init ([0] int32 i,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [1] bool CS$4$0000)\n\u00a0 IL_0000:\u00a0 nop\n\u00a0 IL_0001:\u00a0 ldc.i4.0\n\u00a0 IL_0002:\u00a0 stloc.0\n\u00a0 IL_0003:\u00a0 br.s\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IL_0021\n\u00a0 IL_0005:\u00a0 nop\n\u00a0 IL_0006:\u00a0 ldstr\u00a0\u00a0\u00a0\u00a0\u00a0 \"Number: {0}\"\n\u00a0 IL_000b:\u00a0 ldloc.0\n\u00a0 IL_000c:\u00a0 box\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [mscorlib]System.Int32\n\u00a0 IL_0011:\u00a0 call\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string [mscorlib]System.String::Format(string,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 object)\n\u00a0 IL_0016:\u00a0 call\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 void [mscorlib]System.Console::WriteLine(string)\n\u00a0 IL_001b:\u00a0 nop\n\u00a0 IL_001c:\u00a0 nop\n\u00a0 IL_001d:\u00a0 ldloc.0\n\u00a0 IL_001e:\u00a0 ldc.i4.1\n\u00a0 IL_001f:\u00a0 add\n\u00a0 IL_0020:\u00a0 stloc.0\n\u00a0 IL_0021:\u00a0 ldloc.0\n\u00a0 IL_0022:\u00a0 ldc.i4.s\u00a0\u00a0 10\n\u00a0 IL_0024:\u00a0 clt\n\u00a0 IL_0026:\u00a0 stloc.1\n\u00a0 IL_0027:\u00a0 ldloc.1\n\u00a0 IL_0028:\u00a0 brtrue.s\u00a0\u00a0 IL_0005\n\u00a0 IL_002a:\u00a0 ret\n} \/\/ end of method Program::DoSomething\n<\/pre>\n<\/li>\n<li>Bytes that the CPU understands, i.e. machine code:\n<pre class=\"lang:c# theme:vs2012\">55 8b ec 83 ec 18 83 3d 14 2e 92 00 00 74 05 e8 c5 a3 f4 76 33 d2 89 55-fc c7 45 f8 00 00 00 00 90 33 d2 89 55 fc 90 eb 41 90 b9 38 2b 33 79 e8 40 1f 79 fd 89 45 f4 8b 05 30 20 fb 01 89 45 ec 8b 45 f4 8b 55 fc 89 50 04 8b 45 f4 89 45 e8 8b 4d ec 8b 55 e8 e8 ee 72 13 76 89 45 f0 8b 4d f0 e8 cb 36 61 76 90 90 ff 45 fc 83 7d fc 0a 0f 9c c0 0f b6 c0 89 45 f8 83-7d f8 00 75 ac 90 8b e5 5d c3<\/pre>\n<\/li>\n<\/ol>\n<p>The debuggers decode the bytes and display the assembly, as shown below. This is a little cryptic at first, but is still much more readable than the machine code you see in step 3.\u00a0 You&#8217;ll notice that the second column shows the bytes as listed above, 55 8b ec&#8230;<\/p>\n<pre class=\"lang:xhtml theme:github\">031800a8 55\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0push\u00a0\u00a0\u00a0 ebp  \n031800a9 8bec\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 ebp,esp\n031800ab 83ec18\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sub\u00a0\u00a0\u00a0\u00a0 esp,18h\n031800ae 833d142e920000\u00a0 cmp\u00a0\u00a0\u00a0\u00a0 dword ptr ds:[922E14h],0\n031800b5 7405\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 je\u00a0\u00a0\u00a0\u00a0\u00a0 031800bc\n031800b7 e8c5a3f476\u00a0\u00a0\u00a0\u00a0\u00a0 call\u00a0\u00a0\u00a0 mscorwks!JIT_DbgIsJustMyCode (7a0ca481)\n031800bc 33d2\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xor\u00a0\u00a0\u00a0\u00a0 edx,edx\n031800be 8955fc\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-4],edx\n031800c1 c745f800000000\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-8],0\n031800c8 90\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nop\n031800c9 33d2\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xor\u00a0\u00a0\u00a0\u00a0 edx,edx\n031800cb 8955fc\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-4],edx\n031800ce 90\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nop\n031800cf eb41\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 jmp\u00a0\u00a0\u00a0\u00a0 03180112\n031800d1 90\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nop\n031800d2 b9382b3379\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 ecx,offset mscorlib_ni+0x272b38 (79332b38) (MT: System.Int32)\n031800d7 e8401f79fd\u00a0\u00a0\u00a0\u00a0\u00a0 call\u00a0\u00a0\u00a0 0091201c (JitHelp: CORINFO_HELP_NEWSFAST)\n031800dc 8945f4\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-0Ch],eax\n031800df 8b053020fb01\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 eax,dword ptr ds:[1FB2030h] (\"Number: {0}\")\n031800e5 8945ec\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-14h],eax\n031800e8 8b45f4\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0mov\u00a0\u00a0\u00a0\u00a0 eax,dword ptr [ebp-0Ch]\n031800eb 8b55fc\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 edx,dword ptr [ebp-4]\n031800ee 895004\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [eax+4],edx\n031800f1 8b45f4\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 eax,dword ptr [ebp-0Ch]\n031800f4 8945e8\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-18h],eax\n031800f7 8b4dec\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 ecx,dword ptr [ebp-14h]\n031800fa 8b55e8\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 edx,dword ptr [ebp-18h]\n031800fd e8ee721376\u00a0\u00a0\u00a0\u00a0\u00a0 call\u00a0\u00a0\u00a0 mscorlib_ni+0x1f73f0 (792b73f0) (System.String.Format(System.String, System.Object), mdToken: 060001bd)\n03180102 8945f0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-10h],eax\n03180105 8b4df0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 ecx,dword ptr [ebp-10h]\n03180108 e8cb366176\u00a0\u00a0\u00a0\u00a0\u00a0 call\u00a0\u00a0\u00a0 mscorlib_ni+0x6d37d8 (797937d8) (System.Console.WriteLine(System.String), mdToken: 060007c8)\n0318010d 90\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0nop\n0318010e 90\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nop\n0318010f ff45fc\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inc\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-4]\n03180112 837dfc0a\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmp\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-4],0Ah\n03180116 0f9cc0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 setl\u00a0\u00a0\u00a0 al\n03180119 0fb6c0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 movzx\u00a0\u00a0 eax,al\n0318011c 8945f8\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov \u00a0\u00a0\u00a0\u00a0dword ptr [ebp-8],eax\n0318011f 837df800\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmp\u00a0\u00a0\u00a0\u00a0 dword ptr [ebp-8],0\n03180123 75ac\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 jne\u00a0\u00a0\u00a0\u00a0 031800d1\n&lt;?XML:NAMESPACE PREFIX = SKYPE \/&gt;\u00a003180125\u00a090\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nop\n03180126 8be5\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mov\u00a0\u00a0\u00a0\u00a0 esp,ebp\n03180128 5d\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 pop\u00a0\u00a0\u00a0\u00a0 ebp\n03180129 c3\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ret\n<\/pre>\n<p>When debugging without Visual Studio, the important thing to realize\u00a0is that the code you are looking at is no longer in the CLI language in which it was written. It has been optimized, pruned and changed into something that the CPU can understand.\u00a0 Although this sounds a little scary, believe me when I say that debugging at this level, using SOS, is easy and fun.<\/p>\n<p><strong>Read also:<\/strong><br \/><a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/taming-sandcastle-a-net-programmers-guide-to-documenting-your-code\/\" target=\"_blank\" rel=\"noopener\">Sandcastle documentation for .NET projects<\/a><br \/><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/sql-server-deadlocks-by-example\/\" target=\"_blank\" rel=\"noopener\">SQL Server deadlock debugging<\/a><\/p>\n<h2>Let&#8217;s do some debugging<\/h2>\n<p>In order to debug code at this level, it helps to know the basics of assembly language. While you are starting out, you can get by without it, but the more you learn about assembly language, process architecture, and the CLR, the faster and easier you will find debugging.<\/p>\n<p>I have written a test console application with some examples to work through. We will be using the first test, which causes an exception to be thrown that is caught by an empty catch block. When you were to start the example1.exe application, and press 1 followed by enter, you would see the menu being re-printed and it will look like nothing has really happened but , in fact, an exception is being thrown, caught and swallowed. The code that is doing the swallowing looks like this:<\/p>\n<pre class=\"lang:c# theme:vs2012\">try  \n\u00a0 {\u00a0\u00a0 \nAppSettingsReader asr = new AppSettingsReader();\n\u00a0\u00a0 \u00a0\u00a0\u00a0string date = \nasr.GetValue(\"DateFormat\", typeof (string)).ToString();\n\u00a0\u00a0\u00a0\u00a0\u00a0 RunCommand(date);\n\u00a0\u00a0 }catch(Exception)\n\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/HA HA HA No one can hear you scream in here!!!!!!\n\u00a0\u00a0 }\n<\/pre>\n<p>When faced with this situation, we need to attach a debugger.\u00a0 If you haven&#8217;t already got the debuggers, then please go ahead and download <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/debugger\/debugger-download-tools\">the debugging tools for windows<\/a>. I always install mine to c:\\debuggers for ease of use, but install them wherever you like.<\/p>\n<p>I also create a batch file, <b>doDebug.cmd<\/b>, to set my symbol path and add <b>cdb<\/b> to the path variable so I don&#8217;t have to type in c:\\debuggers every time:<\/p>\n<pre>SET _NT_SYMBOL_PATH=srv*c:\\debuggers\\symbols*http:\/\/msdl.microsoft.com\/download\/symbols;  SET PATH=%PATH%;c:\\debuggers\n<\/pre>\n<p>Creating this file, and changing c:\\debuggers to whatever your debugger path is, will save you work.<\/p>\n<p><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/how-to-use-parameters-in-powershell\/\" target=\"_blank\" rel=\"noopener\">PowerShell automation for debugging workflows<\/a><\/p>\n<h2>Stacks and Exceptions<\/h2>\n<p>So let&#8217;s start the <b>example1.exe<\/b> application. If you are running the Visual Studio solution, make sure you start the application by choosing &#8220;Start without debugging&#8221; or, alternatively, double clicking the <b>example1.exe<\/b>.\u00a0 if you choose option 1\u00a0when it is running,\u00a0 the menu will be shown again and it will look like nothing has happened.<\/p>\n<p>From a command prompt, run the <b>doDebug.cmd<\/b> file to set your environment variables and then type &#8220;<b>cdb -pn example1.exe<\/b>&#8220;. This will start <b>cdb<\/b> and instructs it to attach to a process called <b>example1.exe<\/b>. Another common way to start cdb is to issue the command\u00a0 &#8220;<b>cdb -p 1234<\/b>&#8220;, which attaches to the pid 1234.\u00a0 You can attach to any process or service running on the server; if a process is in task manager then it is fair game, but you will need to be an admin or have debugger user privileges:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-1.jpg\" alt=\"841-1.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This starts <b>cdb<\/b>, stops the example1.exe application from running, and awaits your command. Type <b>g<\/b> and then enter, in order to run the process being debugged. Now, choose option 1 again in the example program and, within <b>cdb<\/b>, you should see the message:<\/p>\n<pre>\"(b18.7a4): CLR exception - code e0434f4d (first chance)\"<\/pre>\n<p>This means that there was a <b>first chance CLR exception. <\/b>When debugging, the debugger encounters exceptions before the application hits them. If the debugger allows the application to proceed, and the latter correctly handles the exception, then the application will continue to run normally. If the application does not handle the exception, the debugger sees it a second time, at which point it is termed a &#8216;second chance&#8217; exception, and the application will crash (if allowed to proceed). In our example, we have a <b>try &#8230; catch<\/b> block that quietly handles the exception and reports nothing to the front end.<\/p>\n<p>Let&#8217;s now break into the debugger. \u00a0Set the focus on <b>cbd<\/b> and do ctrl+c to bring up the command prompt.\u00a0 We want to get the debugger to break when it gets a first chance exception, specifically a CLR exception, so type &#8220;<code>sxe e0434f4d\"<\/code>. The <b>sxe<\/b> command (be careful when reading this command out loud!) instructs the debugger to stop execution on encountering an exception ,and the code that follows it is the one from the output above.\u00a0 Press <b>g<\/b> and enter to run the debugger and then choose number <b>1 <\/b>again in the example app.<\/p>\n<p>Now\u00a0 the debugger should break at the point of the exception, so here we go with some real debugging.\u00a0 The <code>sos.dll<\/code> is our saviour in these situations, but first need to load it up, so type &#8220;.<code>loadby SOS mscorwks<\/code>&#8221; (note the full stop before loadby).<\/p>\n<p>This should give no errors and return you to the prompt.\u00a0 Let&#8217;s now take a look at the managed stack trace, If you enter <code>\"!CLRStack<\/code>&#8221; (it is case sensitive), you should see something like this:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-2.jpg\" alt=\"841-2.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>The memory addresses will probably be different but the stack trace should be the same.\u00a0 This tells us where the exception occurred. Excellent! Now we need to see the exception.\u00a0 There are a number of ways to find the exception: Because we instructed the debugger to cease running the application when an exception is thrown, we can use the command &#8220;<code>!pe<\/code>&#8221; to print the most recent exception on the current thread.\u00a0 If you go ahead and run &#8220;<b>!pe<\/b>&#8221; you should see:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-3.jpg\" alt=\"841-3.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This shows that we are getting an invalid <b>DateTime<\/b> format and, together with the<b> !CLRStack<\/b> output, we know that we are in &#8220;<b>DateTime.Parse<\/b>&#8221; , so the root cause of the problem is beginning to get a little clearer. Let&#8217;s do another stack trace, but this time we will add <code>\"-p<\/code>&#8220;<code>, <\/code>i.e. &#8220;!<code>CLRStack -p<\/code>&#8220;, which shows the parameters passed in:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-4.jpg\" alt=\"841-4.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>Unfortunately, sometimes, we don&#8217;t get as much help as we would like, and in this case we don&#8217;t see the value of the parameters.\u00a0 However, there is a more scatter-gun approach to finding parameters and it is to use &#8220;<code>!DumpStackObjects<\/code>&#8221; (!dso for short), which will walk the stack and dump any objects it finds:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-5.jpg\" alt=\"841-5.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>You will see, at the top of the screen, that we get the Exception object and then a String, which looks like it contains an invalid date format, &#8220;<code>32 January 2009<\/code>&#8220;. Bingo!\u00a0 If we now execute &#8220;<code>!CLRStack -l<\/code>&#8220;, the debugger will list the memory addresses of all the variables. We can look through these to find which method has a local variable which points to our string. We do this by reading the stack output for the memory address of the bad string, &#8220;<code>0x01d03fdc<\/code>&#8220;, which is in the second column of the <code>\"!dso<\/code>&#8221; output.\u00a0 Here we can see that it is a local variable in example1.<code>DotNetSwallowed.SwallowCommand().<\/code><\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-6.jpg\" alt=\"841-6.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>Armed with knowledge of where the string is being set, and what is passed through to <b>RunCommand<\/b> and <b>DateTime.Parse<\/b>, you can work out why the date is in the incorrect format by delving into the source code. If you do not have access to the source code, you can use .NET Reflector where, even if the code is obfuscated, you still have the correct class and method names, so it should be fairly straight forward.<\/p>\n<h2>Viewing Objects<\/h2>\n<p>Using this simple method, we have learnt how to break on an exception, examine the CLR stack and list all the objects on the stack.\u00a0 There is one more major, common task that we haven&#8217;t looked at, and that is examining specific objects.\u00a0 In .NET, there are two kinds of variables, <b>reference types<\/b> and <b>value types<\/b>.\u00a0 Viewing reference types is simple; all you need to do is use the command <code>\"!DumpObject\"<\/code>, or &#8220;<code>!do<\/code>&#8221; for short.<\/p>\n<p>When we call <code>\"!do<\/code>&#8220;, we need to pass an address of an object. If you take the address from the last step, in my case &#8220;<code>0x01d03fdc<\/code>&#8220;, and pass that in, then you can see the underlying object structure:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-8.jpg\" alt=\"841-8.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This shows the text in the <b>System.String<\/b> object, and I have also highlighted the <b>Offset<\/b> column, because as good as <code>sos.dll<\/code> is, it isn&#8217;t magic.\u00a0 It retrieves the type of the object by looking up the &#8220;Method Table&#8221;, which basically describes how the type is laid out in memory.\u00a0 The method table address is stored in the first 4 bytes of the object.<\/p>\n<p>We are now going to examine how &#8220;<code>!do<\/code>&#8221; works, so if instead of using &#8220;!<code>do address\"<\/code>, enter<code> \"dc address<\/code>&#8220;. Note that there is no exclamation or full stop; <b>dc<\/b> is a native debugger command to dump the memory, showing dwords (or Int32&#8217;s):<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-9.jpg\" alt=\"841-9.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>The green highlighted text is the Method Table, which should have been listed when you executed &#8220;<code>!do\"<\/code>.\u00a0 The numbers displayed are in hex so 19 translates to 25, 18 to 24 and so on.\u00a0 So, if you knew you wanted to dump out a string, but didn&#8217;t want to use SOS (crazy fool), then because the offset to the first character (<code>m_fisrtChar)<\/code> is <b>+0xC<\/b> (or 12 bytes to you and me) you could do:\u00a0<code> du memoryAddress+0xC <\/code>(<b>du<\/b> is a different format of <b>d<\/b>, which dumps unicode strings):<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-10.jpg\" alt=\"841-10.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>You can see the string and we didn&#8217;t even have to use SOS. SOS can help us decode structures, but it&#8217;s important to remember that which we can still do it manually if we have to.<\/p>\n<p>Hopefully this shows how <code>sos.dll<\/code> decodes specific reference types.<\/p>\n<p>Now, I mentioned that there were also <b>value types<\/b>. These are not passed by reference, probably\u00a0 for performance reasons, and so the method table isn&#8217;t passed around with the variable. Therefore, we need to tell SOS what the type is. If you don&#8217;t know what the type is, then I am afraid you are out of luck and stuck with using &#8220;<code>dc address<\/code>&#8220;, which will give you the actual bytes and a string representation.<\/p>\n<p>If you use<code> !dso<\/code> to get the memory address of the &#8220;<code>example1.DotNetSwallowed<\/code>&#8221; object and then do &#8220;!<code>do address<\/code>&#8221; you get:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-11.jpg\" alt=\"841-11.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>It has one variable <b>_initialized<\/b>, and the offset is 4, so try doing &#8220;<b>!do address+0x4<\/b>&#8221; and you should get this error:<\/p>\n<div class=\"NOTE\">\n<p class=\"NOTE\"><strong>Note:<\/strong> this object has an invalid CLASS field<\/p>\n<\/div>\n<h2>Invalid object<\/h2>\n<p>This means that it doesn&#8217;t have a method table. We could use the details from when the <code>DotNetSwallowed<\/code> was dumped out and SOS did the work for us, but let&#8217;s do this ourselves to show how it works. What we do know is that it is a &#8220;<b>System.Boolean<\/b>&#8221; so to look up the method table we use one of my favourite SOS commands &#8220;<b>!Name2EE<\/b>&#8221; (or Name 2 Ed Elliott as I like to call it):<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-12.jpg\" alt=\"841-12.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This goes through all modules looking for the type. If you know which module it is in, you can use that instead of *.\u00a0 When you have the method table you can then use &#8220;<b>!DumpVC<\/b>&#8221; to dump out the value type:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-13.jpg\" alt=\"841-13.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This shows the value of the value type, in this case 1, which is true.<\/p>\n<h2>Breakpoints<\/h2>\n<p>The ability to set breakpoints is a basic debugging requirement. <b>cdb<\/b> is capable of setting them and so is <code>sos.dll<\/code>, but the actual breakpoints are set using the native address not the MSIL address.\u00a0 To set a breakpoint we can use the &#8220;<b>!bpmd<\/b>&#8221; command, which takes either a module and method name or a &#8220;Method Description&#8221;.<\/p>\n<p>A method description is used by a range of SOS commands so it is useful to talk about them at this point.\u00a0 There are a number of ways to get the method description; the first is to use &#8220;<b>Name2EE<\/b>&#8221; to get the method table, and then get a list of the method descriptions associated with that type. For example, if you go back to <b>cdb<\/b> and do: &#8220;<b>!Name2EE * example1.DotNetSwallowed<\/b>&#8220;, it will show the method table&#8217;s address. instead, do &#8220;<b>!DumpMT -md<\/b>&#8220;, which dumps out the method table and also lists the method descriptions:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-14.jpg\" alt=\"841-14.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>This shows the methods on the type, and whether they have yet been compiled into binary (Jit or PreJit).\u00a0 Let&#8217;s go ahead and dump out one of the method descriptions. We&#8217;ll use the SwallowCommand md and do &#8220;<b>!DumpMD address<\/b>&#8220;:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-15.jpg\" alt=\"841-15.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>If the code has been JIT&#8217;d then, under <b>CodeAddr<\/b>, we get the address of the binary bytes, and we can use the native <b>cdb<\/b> command to set the breakpoint using &#8220;bp&#8221;. For example, in this case we could use &#8220;bp 01ae0218&#8221;. If it hasn&#8217;t been JIT&#8217;d,or if you want to use SOS, execute &#8220;<b>!bpmd -md methodDesc<\/b>&#8220;, using the method description highlighted in green.\u00a0 It should show that it is setting the breakpoint. To verify that it has set one, use the <b>cdb<\/b> command &#8220;<b>bl<\/b>&#8221; to list all of the break points.<\/p>\n<p>With the breakpoint set, press &#8220;g&#8221; and then, in the example application, choose option 1. This time, you will see that the debugger breaks before the exception, with the message &#8220;Breakpoint 0 hit&#8221;. Do a &#8220;<b>!CLRStack<\/b>&#8221; to confirm where you are and to get a list of variables. The easiest thing to do is &#8220;<b>!dso<\/b>&#8221; so you can see all the objects on the stack including the &#8220;this pointer&#8221;, which will be in the stack as the name of the class itself, so you can then dump that out to see what state it is in.<\/p>\n<p>To remove the breakpoint, just do &#8220;<b>bc X<\/b>&#8221; where x is the breakpoint number.\u00a0 You can also do &#8220;<b>bd<\/b>&#8221; which disables it until you call &#8220;<b>be<\/b>&#8220;.<\/p>\n<h2>Viewing IL code<\/h2>\n<p>If we have a method description, we can easily view the MSIL using the command &#8220;<b>!DumpIL methodDescriptionAddress<\/b>&#8220;<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/841-16.jpg\" alt=\"841-16.jpg\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>The codes you see, such as <b>ldloc.1<\/b>, is documented on MSDN (Link: <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.reflection.emit.opcodes_fields.aspx\">http:\/\/msdn.microsoft.com\/en-us\/library\/system.reflection.emit.opcodes_fields.aspx<\/a>), under &#8220;<b>OpCodes Fields<\/b>&#8220;<\/p>\n<h2>Painless Debugging<\/h2>\n<p>So there you have it! Using a few simple commands, we have been able to find out why a program is doing nothing. By examining the exception that is being swallowed we could see that it was an invalid date format.\u00a0 We could then take this information to find out where the string is coming from and fix it.<\/p>\n<p>To summarise the process here are the steps again:<\/p>\n<ol>\n<li>Attach the debugger &#8211; <b>cdb<\/b> -pn example1.exe<\/li>\n<li>Tell the debugger to break on CLR exceptions &#8211; sxe e0434f4d<\/li>\n<li>Have a look at the exception &#8211; !pe<\/li>\n<li>Look for objects on the stack which might explain it &#8211; !dso<\/li>\n<li>Look at a specific reference type &#8211; !do address<\/li>\n<li>Find a method table for a value type &#8211; !Name2EE * System.Boolean<\/li>\n<li>Show the value of that type and variable &#8211; !DumpVC methodTable address<\/li>\n<li>Find a method description using a method table &#8211; !DumpMT -md methodTable<\/li>\n<li>Set a break point &#8211; !bpmd -md methodDescription<\/li>\n<li>Have a look at the MSIL &#8211; !DumpIL methodDescription<\/li>\n<\/ol>\n<p>I have shown a few of the native debugger and SOS commands and how they can be used to understand and examine the failing process.\u00a0 This may seem a little overwhelming at first but it is straight forward and well documented.<\/p>\n<h2>sos.dll in Visual Studio<\/h2>\n<p>It is important to remember that SOS is not part of the debugging tools; it is part of the .NET framework and now even ships with each version.\u00a0 Although not as elegant and as easy to use as in <b>cdb<\/b>, the helper can be loaded in Visual Studio by using the immediate window and typing &#8220;.load C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\<code>sos.dll<\/code>&#8221; (change the path to the version of the framework that you are using).<\/p>\n<p>SOS has a number of exciting features and I would encourage anyone to look further into debugging using <code>sos.dll<\/code> as it will come in handy at some point. If you are interested in learning more ,then these functions are a good place to start:<\/p>\n<ul>\n<li><b>&#8220;!DumpHeap -stat&#8221;<\/b> &#8211; List all objects in memory and show how many of and how much memory each type is using so you can track down memory leaks.<\/li>\n<li>&#8220;!<b>GCRoot [address]<\/b>&#8221; &#8211; Find where a specific object is referenced so you can track down memory leaks easily<\/li>\n<li><b>&#8220;!SyncBlk&#8221;<\/b> &#8211; Show where threads are waiting on locks to diagnose deadlocks.<\/li>\n<li><b>&#8220;!Help [command]&#8221;<\/b> a very handy reference and examples on how to use each function that comes with the tools.<\/li>\n<li><b>&#8220;!Help faq&#8221; <\/b>Have a look, it is one of the most useful help commands I have ever seen from Microsoft.<\/li>\n<\/ul>\n<h2>Further Reading<\/h2>\n<ul class=\"reference-list\">\n<li>The help file that comes with the debuggers is an invaluable source of information, there are many different debugger commands and ways to use them that there is usually at least a couple of ways to do everything.<\/li>\n<li>Msdn <code>sos.dll<\/code> documentation (Link: <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/tools\/sos-dll-sos-debugging-extension\">SOS Debugging Extension (<code>sos.dll<\/code>)<\/a>)<\/li>\n<li>Tess Ferrandez is a Microsoft Escalation engineer who regulary blogs about using the debugging tools and <code>sos.dll<\/code> (Link: <a href=\"http:\/\/blogs.msdn.com\/tess\/\">http:\/\/blogs.msdn.com\/tess\/<\/a>)<\/li>\n<li>John Robbins is the master of debugging and he has a number of books on the subject as well as a blog (<a href=\"https:\/\/www.wintellect.com\/author\/jrobbins\/\">https:\/\/www.wintellect.com\/author\/jrobbins\/<\/a>)<\/li>\n<li>(Link: <a href=\"http:\/\/www.microsoft.com\/mspress\/books\/5822.aspx\">http:\/\/www.microsoft.com\/mspress\/books\/5822.aspx<\/a>)<\/li>\n<li>Although more for the kernel \/ driver developers the sysinternals series gives a very good reference for process architecture (Link: <a href=\"http:\/\/technet.microsoft.com\/en-us\/sysinternals\/bb963901.aspx\">http:\/\/technet.microsoft.com\/en-us\/sysinternals\/bb963901.aspx<\/a>)<\/li>\n<\/ul>\n<\/div>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: .NET debugging? Don&#039;t give me none of your VS<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do you debug a .NET application without Visual Studio?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Install the Windows Debugging Tools (free from Microsoft), which include WinDbg (GUI) and CDB (command-line). Attach to a running process with &#8220;cdb -pn yourapp.exe&#8221;, load the SOS.dll extension with &#8220;.loadby sos clr&#8221;, and use SOS commands like !clrstack (managed call stack), !dumpheap (heap objects), and !pe (print exception) to inspect the managed runtime. These tools work on production servers where Visual Studio isn\u2019t installed.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is SOS.dll and how do you use it for .NET debugging?<\/h3>\n            <div class=\"faq-answer\">\n                <p>SOS.dll (Son of Strike) is a debugging extension that ships with the .NET Framework and understands managed code concepts like objects, threads, and the garbage collector. Load it in WinDbg or CDB with &#8220;.loadby sos clr&#8221; after attaching to a .NET process. Key commands include !threads (list managed threads), !clrstack (show managed call stack), !dumpobj (inspect an object), and !gcroot (find what\u2019s keeping an object alive for memory leak investigation).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do you analyze a .NET crash dump?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Create a crash dump with Task Manager (right-click process \u2192 Create dump file) or programmatically with MiniDumpWriteDump. Open the dump in WinDbg with File \u2192 Open Crash Dump, load SOS.dll, then use !pe to see the exception that caused the crash, !clrstack to see what code was running, and !dumpheap -stat to check for memory issues. Crash dumps let you debug post-mortem without reproducing the issue.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Guide to debugging .NET applications without Visual Studio using WinDbg, CDB, and SOS.dll. Covers attaching to processes, reading crash dumps, inspecting managed heaps, and debugging exceptions in production.&hellip;<\/p>\n","protected":false},"author":59464,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,5071,4229],"coauthors":[11314],"class_list":["post-707","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-net-debugging","tag-net-framework"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/707","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/59464"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=707"}],"version-history":[{"count":15,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/707\/revisions"}],"predecessor-version":[{"id":108999,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/707\/revisions\/108999"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=707"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=707"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=707"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=707"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}