Where to Learn About VS Debugger 'Magic Names'

Where to learn about VS debugger 'magic names'

These are undocumented implementation details of the compiler, and subject to change at any time. (UPDATE: See GeneratedNames.cs
in the C# sources for the current details; the description below is somewhat out-of-date.)

However, since I'm a nice guy, here are some of those details:

If you have an unused local variable that the optimizer removes, we emit debug info for it anyway into the PDB. We stuck the suffix __Deleted$ onto such variables so that the debugger knows that they were in source code but not represented in the binary.

Temporary variable slots allocated by the compiler are given names with the pattern CS$X$Y, where X is the "temporary kind" and Y is the number of temporaries allocated so far. The temporary kinds are:

0 --> short lived temporaries
1 --> return value temporaries
2 --> temporaries generated for lock statements
3 --> temporaries generated for using statements
4 --> durable temporaries
5 --> the result of get enumerator in a foreach
6 --> the array storage in a foreach
7 --> the array index storage in a foreach.

Temporary kinds between 8 and 264 are additional array index storages for multidimensional arrays.

Temporary kinds above 264 are used for temporaries involving the fixed statement fixing a string.

Special compiler-generated names are generated for:

1 --> the iterator state ("state")
2 --> the value of current in an iterator ("current")
3 --> a saved parameter in an iterator
4 --> a hoisted 'this' in an iterator ("this")
5 --> a hoisted local in an iterator
6 --> the hoisted locals from an outer scope
7 --> a hoisted wrapped value ("wrap")
8 --> the closure class instance ("locals")
9 --> the cached delegate instance ("CachedAnonymousMethodDelegate")
a --> the iterator instance ("iterator")
b --> an anonymous method
c --> anonymous method closure class ("DisplayClass")
d --> iterator class
e --> fixed buffer struct ("FixedBuffer")
f --> anonymous type ("AnonymousType")
g --> initializer local ("initLocal")
h --> query expression temporary ("TransparentIdentifier")
i --> anonymous type field ("Field")
j --> anonymous type type parameter ("TPar")
k --> auto prop field ("BackingField")
l --> iterator thread id
m --> iterator finally ("Finally")
n --> fabricated method ("FabricatedMethod")
o --> dynamic container class ("SiteContainer")
p --> dynamic call site ("Site")
q --> dynamic delegate ("SiteDelegate")
r --> com ref call local ("ComRefCallLocal")
s --> lock taken local ("LockTaken")

The pattern for generating magical names is: P<N>C__SI where:

  • P is CS$ for cached delegates and display class instances, empty otherwise.
  • N is the original name associated with the thing, if any
  • C is the character 1 through s listed above
  • S is a descriptive suffix ("current", "state", and so on) so that you don't have to have the table above memorized when reading the metadata.
  • I is an optional unique number

Where do I learn what I need to know about C++ compilers?

Concerning the "numerous options of the various compilers"
A piece of good news: you needn't worry about the detail of most of these options. You will, in due time, delve into this, only for the very compiler you use, and maybe only for the options that pertain to a particular set of features. But as a novice, generally trust the default options or the ones supplied with the make files.

The broad categories of these features (and I may be missing a few) are:

  • pre-processor defines (now, you may need a few of these)
  • code generation (target CPU, FPU usage...)
  • optimization (hints for the compiler to favor speed over size and such)
  • inclusion of debug info (which is extra data left in the object/binary and which enables the debugger to know where each line of code starts, what the variables names are etc.)
  • directives for the linker
  • output type (exe, library, memory maps...)
  • C/C++ language compliance and warnings (compatibility with previous version of the compiler, compliance to current and past C Standards, warning about common possible bug-indicative patterns...)
  • compile-time verbosity and help

Concerning an inventory of compilers with their options and features

I know of no such list but I'm sure it probably exists on the web. However, suggest that, as a novice you worry little about these "details", and use whatever free compiler you can find (gcc certainly a great choice), and build experience with the language and the build process. C professionals may likely argue, with good reason and at length on the merits of various compilers and associated runtine etc., but for generic purposes -and then some- the free stuff is all that is needed.

Concerning the build process

The most trivial applications, such these made of a single unit of compilation (read a single C/C++ source file), can be built with a simple batch file where the various compiler and linker options are hardcoded, and where the name of file is specified on the command line.

For all other cases, it is very important to codify the build process so that it can be done

  a) automatically and

  b) reliably, i.e. with repeatability.

The "recipe" associated with this build process is often encapsulated in a make file or as the complexity grows, possibly several make files, possibly "bundled together in a script/bat file.

This (make file syntax) you need to get familiar with, even if you use alternatives to make/nmake, such as Apache Ant; the reason is that many (most?) source code packages include a make file.

In a nutshell, make files are text files and they allow defining targets, and the associated command to build a target. Each target is associated with its dependencies, which allows the make logic to decide what targets are out of date and should be rebuilt, and, before rebuilding them, what possibly dependencies should also be rebuilt. That way, when you modify say an include file (and if the make file is properly configured) any c file that used this header will be recompiled and any binary which links with the corresponding obj file will be rebuilt as well. make also include options to force all targets to be rebuilt, and this is sometimes handy to be sure that you truly have a current built (for example in the case some dependencies of a given object are not declared in the make).

On the Pre-processor:
The pre-processor is the first step toward compiling, although it is technically not part of the compilation. The purposes of this step are:

  • to remove any comment, and extraneous whitespace
  • to substitute any macro reference with the relevant C/C++ syntax. Some macros for example are used to define constant values such as say some email address used in the program; during per-processing any reference to this constant value (btw by convention such constants are named with ALL_CAPS_AND_UNDERSCORES) is replace by the actual C string literal containing the email address.
  • to exclude all conditional compiling branches that are not relevant (the #IFDEF and the like)

What's important to know about the pre-processor is that the pre-processor directive are NOT part of the C-Language proper, and they serve several important functions such as the conditional compiling mentionned earlier (used for example to have multiple versions of the program, say for different Operating Systems, or indeed for different compilers)

Taking it from there...
After this manifesto of mine... I encourage to read but little more, and to dive into programming and building binaries. It is a very good idea to try and get a broad picture of the framework etc. but this can be overdone, a bit akin to the exchange student who stays in his/her room reading the Webster dictionary to be "prepared" for meeting native speakers, rather than just "doing it!".

Where do I learn more about WPF and Storyboards?

You can find a lot of basic information at this CodeProject article series. And this one talks about storyboard animation.

Where to learn about VS debugger 'magic names'

These are undocumented implementation details of the compiler, and subject to change at any time. (UPDATE: See GeneratedNames.cs
in the C# sources for the current details; the description below is somewhat out-of-date.)

However, since I'm a nice guy, here are some of those details:

If you have an unused local variable that the optimizer removes, we emit debug info for it anyway into the PDB. We stuck the suffix __Deleted$ onto such variables so that the debugger knows that they were in source code but not represented in the binary.

Temporary variable slots allocated by the compiler are given names with the pattern CS$X$Y, where X is the "temporary kind" and Y is the number of temporaries allocated so far. The temporary kinds are:

0 --> short lived temporaries
1 --> return value temporaries
2 --> temporaries generated for lock statements
3 --> temporaries generated for using statements
4 --> durable temporaries
5 --> the result of get enumerator in a foreach
6 --> the array storage in a foreach
7 --> the array index storage in a foreach.

Temporary kinds between 8 and 264 are additional array index storages for multidimensional arrays.

Temporary kinds above 264 are used for temporaries involving the fixed statement fixing a string.

Special compiler-generated names are generated for:

1 --> the iterator state ("state")
2 --> the value of current in an iterator ("current")
3 --> a saved parameter in an iterator
4 --> a hoisted 'this' in an iterator ("this")
5 --> a hoisted local in an iterator
6 --> the hoisted locals from an outer scope
7 --> a hoisted wrapped value ("wrap")
8 --> the closure class instance ("locals")
9 --> the cached delegate instance ("CachedAnonymousMethodDelegate")
a --> the iterator instance ("iterator")
b --> an anonymous method
c --> anonymous method closure class ("DisplayClass")
d --> iterator class
e --> fixed buffer struct ("FixedBuffer")
f --> anonymous type ("AnonymousType")
g --> initializer local ("initLocal")
h --> query expression temporary ("TransparentIdentifier")
i --> anonymous type field ("Field")
j --> anonymous type type parameter ("TPar")
k --> auto prop field ("BackingField")
l --> iterator thread id
m --> iterator finally ("Finally")
n --> fabricated method ("FabricatedMethod")
o --> dynamic container class ("SiteContainer")
p --> dynamic call site ("Site")
q --> dynamic delegate ("SiteDelegate")
r --> com ref call local ("ComRefCallLocal")
s --> lock taken local ("LockTaken")

The pattern for generating magical names is: P<N>C__SI where:

  • P is CS$ for cached delegates and display class instances, empty otherwise.
  • N is the original name associated with the thing, if any
  • C is the character 1 through s listed above
  • S is a descriptive suffix ("current", "state", and so on) so that you don't have to have the table above memorized when reading the metadata.
  • I is an optional unique number

Visual Studio Debugger - any way to access compiler-generated temporary variables through the debugger?

Unfortunately there is no way to do this with the C# EE. The names for these locals are indeed stored in the PDB and available. However the C# EE will filter out all temporary values during debugging to reduce clutter. This filtering is unconditional and cannot be overridden. C# is not alone here as this is the behavior in every language.

The good news though is that every language uses different naming patterns for their temporaries. This means a temporary name in C# will run right past the filtering of the VB EE. Even though it's an illegal identifier the VB EE still considers it a valid local (and vice versa). Hence you just need to temporarily switch the debugging engine for C# code to the VB EE and the locals will become visible

Here is how to do this

  • Close all instances of Visual Studio (this is really important)
  • Open up regedit
  • Navigate to HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0_Config\AD7Metrics\ExpressionEvaluator\{3F5162F8-07C6-11D3-9053-00C04FA302A1}\{994B45C4-E6E9-11D2-903F-00C04FA302A1}
  • Change the value of CLSID from {60F5556F-7EBC-4992-8E83-E9B49187FDE3} to {59924502-559A-4BB1-B995-5D511BB218CD}

Now whenever you debug C# code it will use the VB EE and hence display temporaries as normal locals. Note that this is completely unsupported. But it should work fine. I was able to see raw closure values with this trick on my machine and poke around a bit without any issues.

Note that these instructions are specific to Visual Studio 2013. If you are using 2012, or 2010 it should work by changing the 12.0_Config in the registry key name to

  • 2012 use 11.0_Config
  • 2010 use 10.0_Config

It may need a few tweaks on top of that (didn't actually test older versions). If you run into any problems there let me know and I'll try and get it working locally and update the instructions

Here is a pic of the final output once you make this change

Sample Image

What is this Type in .NET (Reflection)

It's almost certainly a class generated by the compiler due to a lambda expression or anonymous method. For example, consider this code:

using System;

class Test
{
static void Main()
{
int x = 10;
Func<int, int> foo = y => y + x;
Console.WriteLine(foo(x));
}
}

That gets compiled into:

using System;

class Test
{
static void Main()
{
ExtraClass extra = new ExtraClass();
extra.x = 10;

Func<int, int> foo = extra.DelegateMethod;
Console.WriteLine(foo(x));
}

private class ExtraClass
{
public int x;

public int DelegateMethod(int y)
{
return y + x;
}
}
}

... except using <>c_displayClass1 as the name instead of ExtraClass. This is an unspeakable name in that it isn't valid C# - which means the C# compiler knows for sure that it won't appear in your own code and clash with its choice.

The exact manner of compiling anonymous functions is implementation-specific, of course - as is the choice of name for the extra class.

The compiler also generates extra classes for iterator blocks and (in C# 5) async methods and delegates.

What is it about the Think Pascal debugger that makes it so legendary?

Answering this as someone who worked at (pre-Symantec) Think in a minor role while Macintosh Pascal was being developed (1984).

It was revolutionary for two reasons: first, it was an incremental compiler. Getting rid of the edit-compile-link-test cycle is a huge benefit. If you Google Mel Conway (chief scientist at Think), I believe you'll find some notes on the construction of incremental compilers (I looked at his main site before posting and it seems to be under construction).

The second reason was that it was a GUI, when nothing else was, and worked with actual program code. To put this in comparison, I don't believe that symbolic debuggers for MS-DOS or the Mac were available until several years later (I recall one Mac project in 1985/86 where I was constantly interpreting the assembly dumps for the rest of the team), and they're a far cry from source-level debugging. Microsoft released a source-level debugger with Quick C in 1988 (iirc); I never worked with Turbo-Pascal or Turbo-C, so don't know what they had.

Today, pretty much every IDE gives you equal or better debugging features ...



Related Topics



Leave a reply



Submit