How to get line numbers in your code for specific events, viz., exception?

One of my clients came up with a requirement to track down the line of the code immediately when the exception is thrown, note that this is 'without the use of the debuggers'. My research led me to lot of questions online about this and hence I am trying to write a succinct blog about what can be done and what should not be done. This blog applies to MANAGED CODE. The key to getting line numbers in your managed code is PDBs. These are also known as symbol files. What gets stored in PDB is different from native and managed code. Native PDBs hold quite a lot of information and they are "mandatory" for effective native debugging. Managed PDBs contain line numbers and source file names, which may be vital on occasions but not "mandatory". Read this Blog Post to decode your qualms of PDBs.

Now that we know PDBs are very important to get line numbers. We should work with PDBs to get the line numbers. For all praical puposes it's a bad idea to ship PDBs to your client along with your product, there are disadvantages too with this method which I will speak about. I found out three ways to achieve this , one of them is a straight forward way and the other two are quite similar but need some amount of work. So here we go,

1. This is a straight forward way of using .net classes to get line numbers from frames. This requires your PDBs to be with the assemblies i.e in the same folder. You cannot use symbol path or any Environment variable to have them in separate folders. What this essentially means is that, you will have to ship the PDBs too!

catch (Exception ex)

           {   StackTrace st = new StackTrace(ex, true); //Get the first stack frame
                StackFrame frame = st.GetFrame(0);                 //Get the file name
                 fileName = frame.GetFileName();                 //Get the method name
                 methodName = frame.GetMethod().Name;
                 //Get the line number from the stack frame
                 line = frame.GetFileLineNumber();
                string Line = line.ToString();                //Get the column number
                 col = frame.GetFileColumnNumber();
                string Col = col.ToString();
}

Here is the msdn link for the api. Apart from the above disclaimer about not shipping your PDBs there are some obvious drawbacks with this mechanism. Assuming that you have a product which installs dlls into GAC, where would you place the PBs? The foremost requirement for this type of design is to have your pdbs beside your assemblies. Now with this requirement it is a strict "NO" to mess with the GAC structure i.e to place PDBs in GAC.

 

2. The other 2 methods is what I would recommend, especially the current one. During your build process, use Mike Stall's pdb2xml converter, distributed as part of MDbg managed code debugger, and store them some place secure (e.g., source control). When you get a stack trace back from the client, you can query the IL offset from the XML data to determine the relevant line number. If your stack traces get submitted to a website, you can even automate the conversion, so that developers will already be getting fully detailed stack traces by the time the cases hit their inbox or it could be part of your catch block logic. Here is a link online which pretty demonstrates its use.

 

3. Third method is somewhat similar to second method and partialy derives the code from my second point, however it customizes pdb look up for source information. The link details the information.  This pretty much resolves the GAC issue that I mentioned above. The code would be something as below.

catch (System.Exception e)

{

st = new StackTrace(e, true);

StackTraceSymbolProvider stsp = new StackTraceSymbolProvider(searchPath,

SymSearchPolicies.AllowSymbolServerAccess |

SymSearchPolicies.AllowOriginalPathAccess |

SymSearchPolicies.AllowReferencePathAccess |

SymSearchPolicies.AllowRegistryAccess);

Console.WriteLine("Custom stack trace:");

Console.WriteLine(stsp.StackTraceToStringWithSourceInfo(st));

A sample is already written and is here.

The obvious advantage of the second and third method is that they have the ability to isolate the PDBs and you can exercise control over it's access. Developers have to smart in their architecture and use one of the 2 methods to carefully design an effective PDB lookup module. All it really boils down to is apis or libraries utilizing GetReaderforFile2 api . This native api in it's own words provides you extensive search abilities. I hope the above points of discussion aid you in your development! That's all for today, Adios!

-Nandeesh Swami