微软真是个十足的混蛋啊!让我们跟踪Exception到行把!(不明真相群众请入)

 

前言

本文介绍一种使用IL的方式直接跟踪exception到行的方法,让大家对exception不再感到恶心!特别是

System.NullReferenceException: 未将对象引用设置到对象的实例。

问题的导火线

今天在debug的时候,又出现了空指针,我这次真的火了!每次遇到空指针,.net给出的信息总是非常的少,我根本不知道是哪里Throw出来的,只能反复检查代码。

我火了!我要起义!于是,开始寻求一种能够出现exception后知道什么代码报出来的。例如以下代码:

代码
    class Program
    {
        
static void Main(string[] args)
        {
            
try
            {
                
string hello3=null;
                hello3 
= hello3.ToUpper();
            }
            
catch (Exception ex)
            {
                Console.Write(ex.ToString());
            }

        }
    }

 

在release模式下,没有pdb的时候,微软给出的答案是:

System.NullReferenceException: 未将对象引用设置到对象的实例。
   在 Pixysoft.Testdriven.Consoles.Program.Main(String[] args)

 

我靠!鬼才知道哪里出现了空指针?我们的程序比这个复杂多了,在层层代码中、上百行的方法体内,神才知道空指针是什么地方报的。

于是我开始思考如何能够知道程序运行到什么地方出现异常。。

 

1. 首先是想到了aop,对方法体拦截。但是还是不能知道方法内部。

2. 然后想到了反射,问题还是同上。

3. 然后想到了StackTrace trace = new StackTrace(exception, true); 直接获取调用堆栈Frame。可是在没有pdb的时候,frame.GetFileLineNumber() 返回的是0. 傻逼了。

4. 然后想到了一个软件NCover。他就能够知道代码运行到什么阶段。于是开始查Ncover的源码。。不查不知道,一查吓一跳,原来NCover是对我们原方法进行重构,入侵了自己的计数器(c# code),然后动态编译出来的。郁闷。看来NCover要放弃了。

4. 最后,我会想起了曾经做过的IL。终于。。。看到了一丝希望:

int offset = frame.GetILOffset();

在没有pdb的时候,IL的偏移量仍然正常输出。

 

正文

思路大概是:

1. 获取exception的调用堆栈。

2. 获取exception相关的这个方法的方法的IL代码

3. 结合excpetion的IL偏移量和方法的IL,把调用源找出来。

 

代码如下:

 

代码
    class Program
    {
        
static void Main(string[] args)
        {
            
try
            {
                
string hello3 = null;

                hello3 
= hello3.ToUpper();
            }
            
catch (Exception ex)
            {
                
//获取调用堆栈

                StackTrace trace 
= new StackTrace(ex, true);

                StackFrame frame 
= trace.GetFrame(0);

                
int offset = frame.GetILOffset();

                
byte[] il = frame.GetMethod().GetMethodBody().GetILAsByteArray();


                
//获取调用指令

                offset
++;

                
ushort instruction = il[offset++];


                
//打开潘多拉魔盒

                ILGlobals 
global = new ILGlobals();

                
global.LoadOpCodes();


                
//翻译

                OpCode code 
= OpCodes.Nop;

                
if (instruction != 0xfe)
                {
                    code 
= global.SingleByteOpCodes[(int)instruction];
                }
                
else
                {
                    instruction 
= il[offset++];
                    code 
= global.MultiByteOpCodes[(int)instruction];
                    instruction 
= (ushort)(instruction | 0xfe00);
                }


                
//获取方法信息

                
int metadataToken = ReadInt32(il, ref offset);

                MethodBase callmethod 
= frame.GetMethod().Module.ResolveMethod(metadataToken,
                     frame.GetMethod().DeclaringType.GetGenericArguments(),
                     frame.GetMethod().GetGenericArguments());

                
//完成

                Console.WriteLine(callmethod.DeclaringType 
+ "." + callmethod.Name);

                Console.Read();
            }

        }

        
private static int ReadInt32(byte[] il, ref int position)
        {
            
return (((il[position++| (il[position++<< 8)) | (il[position++<< 0x10)) | (il[position++<< 0x18));
        }

    }

    
public class ILGlobals
    {
        
private OpCode[] multiByteOpCodes;

        
private OpCode[] singleByteOpCodes;

        
/// <summary>
        
/// Loads the OpCodes for later use.
        
/// </summary>
        public void LoadOpCodes()
        {
            singleByteOpCodes 
= new OpCode[0x100];
            multiByteOpCodes 
= new OpCode[0x100];
            FieldInfo[] infoArray1 
= typeof(OpCodes).GetFields();
            
for (int num1 = 0; num1 < infoArray1.Length; num1++)
            {
                FieldInfo info1 
= infoArray1[num1];
                
if (info1.FieldType == typeof(OpCode))
                {
                    OpCode code1 
= (OpCode)info1.GetValue(null);
                    
ushort num2 = (ushort)code1.Value;
                    
if (num2 < 0x100)
                    {
                        singleByteOpCodes[(
int)num2] = code1;
                    }
                    
else
                    {
                        
if ((num2 & 0xff00!= 0xfe00)
                        {
                            
throw new Exception("Invalid OpCode.");
                        }
                        multiByteOpCodes[num2 
& 0xff= code1;
                    }
                }
            }
        }

        
/// <summary>
        
/// Retrieve the friendly name of a type
        
/// </summary>
        
/// <param name="typeName">
        
/// The complete name to the type
        
/// </param>
        
/// <returns>
        
/// The simplified name of the type (i.e. "int" instead f System.Int32)
        
/// </returns>
        public static string ProcessSpecialTypes(string typeName)
        {
            
string result = typeName;
            
switch (typeName)
            {
                
case "System.string":
                
case "System.String":
                
case "String":
                    result 
= "string"break;
                
case "System.Int32":
                
case "Int":
                
case "Int32":
                    result 
= "int"break;
            }
            
return result;
        }


        
public OpCode[] MultiByteOpCodes
        {
            
get { return multiByteOpCodes; }
        }

        
public OpCode[] SingleByteOpCodes
        {
            
get { return singleByteOpCodes; }
        }
    }

 

 

这样,输出的结果是:

 

System.String.ToUpper

 

在这里出现了空指针。

 

后续

看到这里,大家应该明白我为啥大骂微软了。

明明所有的信息都能够提供,都在IL里面,但是这个该死的微软就是不提供。给个exception还这么暧昧,让我们不断的浪费时间去debug。

实际上,微软的.net framework完全掌握了我们每一行代码的运行情况,内存情况。怪不得现在出了个VS2010,搞了个什么Intellitrace,所谓历史调试什么的。

 

希望通过这篇文章,唤醒大家,其实我们可以走的更远!

 

技术支持

zc22.cnblogs.com

reborn_zhang@hotmail.com

 

补充 2009-12-25 有位明白真相的群众。哈哈! 

[quote]道法自然:
楼主的语言是稍微激了一点,不过,该文的应用场景估计是:(
1)程序在一台机器上已经开发调试OK了;(2)Release编译并部署到另一台机器。在这里出现了NRE的话,.NET FW是不能给你这么多的信息的,只能重新到开发机器进行调试。所以,LZ这种方法还是不错的。

但是,更明智的方法是进行精密的异常管理。
[/quote]
 

 

posted @ 2009-12-25 01:16    阅读(8235)  评论(88编辑  收藏  举报
IT民工