微软真是个十足的混蛋啊!让我们跟踪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

[email protected]

 

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

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

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

 

你可能感兴趣的:(exception)