• 微软真是个十足的混蛋啊!让我们跟踪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]
     
  • 相关阅读:
    简单计算器--hdu1237(栈的运用)
    Bone Collector
    Red and Black---hdu1312(dfs)
    RTMP规范简单分析
    FFMPEG中最关键的结构体之间的关系
    面向对象与形而上学
    洛谷 P2913 [USACO08OCT]车轮旋转Wheel Rotation
    洛谷 P1889 士兵站队
    洛谷 P1885 Moo
    洛谷 P1683 入门
  • 原文地址:https://www.cnblogs.com/zc22/p/1631773.html
Copyright © 2020-2023  润新知