zoukankan      html  css  js  c++  java
  • Visual Studio 调试技巧

    写在前面:假定你在日常的工作中使用到了Visual Studio,并期望了解一些调试技巧来提高工作效率,也许本文适合你。以下Visual Studio简称vs。

    一、入门

    以最简单的控制台应用程序为例,代码如下:

     1 class Program
     2 {
     3     static void Main(string[] args)
     4     {
     5         int result = Sum(2, 3);
     6         Console.WriteLine("2+3={0}", result);
     7     }
     8 
     9     private static int Sum(int a,int b)
    10     {
    11         return a + b;
    12     }
    13 }

    调试的根本目的是跟踪代码、程序的状态,判断是否按照期望的行为运行。常用的跟踪手段有控制台输出、日志输出以及断点调试。

    1. 控制台输出用于开发环境,可以在vs输出窗口中查看程序输出的内容如下图所示:

    由于是控制台应用程序,Console.WriteLine() 输出的内容不会显示在输出窗口,故采用Trace.WriteLine() 。对非控制台应用程序,Console.WriteLine() 输出的内容会正常显示在输出窗口。

    2. 日志输出用于开发环境和生产环境,但更多用于生产环境,用来收集程序的运行信息。常用的日志组件有Log4Net、NLog以及自定义日志组件。依据问题严重程度大致分为严重错误、错误、警告、信息以及调试信息等几个级别。可结合实际需求灵活配置。

    3. 断点调试多用于开发环境,通过设置断点,让程序在指定的位置暂停,以便观察上下文环境情况。

    以上图为例,添加断点后,鼠标移动到变量名上,可以观察一些变量的值。对于复杂类型的变量,通过选中变量,右键选择快速监视的方式。避免鼠标移动后,监视的信息消失。

    以上三种调试方法中,对于开发环境而言,使用最为频繁的方法当数断点调试。后面以断点调试为主,深入介绍。

    二、进阶

    • 启动外部程序

    要使用断点调试,需要满足一些断点调试的条件。对于可执行程序,如控制台应用程序、窗体应用程序、WPF应用程序以及Web应用程序,启动调试后,可以在期望的位置添加断点。而对于如动态库类型,不可以直接启动调试。想要调试这类项目,有两种方式。一种是可以设置项目属性中的启动操作,指定引用该动态库的可执行程序路径。

    另一种方式是运行调用了动态库的可执行程序,通过附加可执行程序进程的方式来调试。

    • 附加进程

    新建 DllDemo 动态库项目,添加 MyMath 类,添加静态方法 Max(int a,int b) 。代码如下:

     1 using System;
     2 
     3 namespace DllDemo
     4 {
     5     public class MyMath
     6     {
     7         public static int Max(int a,int b)
     8         {
     9             return Math.Max(a, b);
    10         }
    11     }
    12 }

     添加对 DllDemo 动态库项目引用, 并修改控制台应用程序如下。为了方便后续调试,控制台应用程序中添加 Console.Read()。

     1 using DllDemo;
     2 using System;
     3 using System.Diagnostics;
     4 
     5 namespace DebugDemo
     6 {
     7     class Program
     8     {
     9         static void Main(string[] args)
    10         {
    11             Console.WriteLine("等待键盘输入...");
    12 
    13             Console.Read();
    14 
    15             int result = Sum(2, 3);
    16 
    17             Console.WriteLine(string.Format("2+3={0}", result));
    18 
    19             result = MyMath.Max(2, 3);
    20 
    21             Console.WriteLine(string.Format("MyMath.Max(2, 3)={0}", result));
    22         }
    23 
    24         private static int Sum(int a, int b)
    25         {
    26             return a + b;
    27         }
    28     }
    29 }

    运行控制台应用程序DebugDemo.exe ,附加该进程,在合适的位置添加断点。

    • 条件断点

    在多层循环中,有时想要满足一定条件时命中断点。这时,条件断点会比较有效。以下面代码为例,想要index = 10(number > 10)时命中断点。

    private int Sum(int number)
    {
        int result = 0;
    
        for(int index= 0;index<number;index++)
        {
            result += index;
        }
    
        return result;
    }

    在合适的位置,按下F9设置断点。右键红色的断点,选择条件...,在条件中输入 index == 10 然后关闭。运行程序进入循环体后,会在index = 10时,命中断点

    • 即时窗口

    有时,当程序运行起来后,进入了某种上下文环境中。此时,想要跟踪程序在另一种上下文环境的运行情况,可以在即时窗口中直接设置某些上下文环境。代码如下所示:

    internal class People
    {
        public string Name { get; set; }
        public bool IsMale { get; set; }
    }
    
    static void Main(string[] args)
    {
        PrintSex(new People() { Name = "张三",IsMale =true});
    }
    
    private static void PrintSex(People people)
    {
        if (people.IsMale)
        {
            Console.WriteLine("{0} is 男性", people.Name);
        }
        else
        {
            Console.WriteLine("{0} is 女性", people.Name);
        }
    }

    程序运行以后,会进入people为男性的分支。在 if (people.IsMale) 行设置断点,当进入断点以后,在即时窗口中更改 people.IsMale 的值后按下Enter键执行,使程序进入另一个分支。

    • 更改执行顺序

    以即时窗口涉及到的代码为例,无论上下文环境如何,想要直接进入 people.IsMale 的分支,可在if (people.IsMale) 行设置断点。进入断点后,在红色断点处。直接按住鼠标左键,拖动到 Console.WriteLine("{0} is 女性", people.Name); 行,进入该分支。

    • 查看调用堆栈

    当程序包含接口继承、抽象类继承等逻辑,导致结构过于复杂,知道功能入口以及出口,想要了解过程时,调用堆栈会比较有用。以下面代码为例:

    private static void DoWork()
    {
        DoWork1();
    }
    
    private static void DoWork1()
    {
        DoWork2();
    }
    
    private static void DoWork2()
    {
        DoWork3();
    }
    
    private static void DoWork3()
    {
        Console.Write("DoWork3");
    }

    假设知道功能入口为DoWork,功能结果为DoWork3,想要了解DoWork3的调用逻辑,可以在DoWork3中设置断点,启动调试后打开调用堆栈窗口,如下图:

    • 异常设置

    当程序运行以后,结果不是预期的。初步猜测发生了异常,由于某些原因,捕获了异常,却未妥善处理,导致异常信息被“吞”掉。此时,异常设置会格外有效。以下面代码为例:

    private static void TryToDivideByZero()
    {
        try
        {
            int a = 9;
            int b = 0;
            int c = a / b;
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    由于方法中存在异常,又有异常捕获,后续逻辑会被打断,此时对异常设置做如下设置:

    重新调试程序会有如下结果,方便快速定位异常发生点。

    三、高级

    在某些场景下,开发环境运行正常,非开发环境运行异常,依赖常规手段无法定位问题原因,想要断点调试,非开发环境运行缺少VS时,远程调试会比较有效。

    在VS安装目录下拷贝远程调试所需的文件夹x86,x64到非开发环境

    依据远程目标机系统环境,运行x86/x64文件夹下msvsmon.exe,选择工具中的选项菜单做如下配置:

    运行待调试程序后,在VS中选择调试>附加到进程(ctrl+alt+p),设置连接类型,连接目标(远程ip地址或计算机名)后查找,会自动列出相关内容。

    在可用进程中选择对应的进程

    在合适的位置添加断点即可开始调试了

  • 相关阅读:
    Andorid手机振动器(Vibrator)的使用
    Log.i()的用法
    刀哥多线程现操作gcd-10-delay
    刀哥多线程全局队列gcd-09-global_queue
    刀哥多线程Barrier异步gcd-08-barrier_async
    刀哥多线程同步任务作用gcd-07-sync_task
    刀哥多线程之主队列gcd-06-main_queue
    刀哥多线程之并发队列gcd-05-dispatch_queue_concurrent
    刀哥多线程串行队列gcd-04-dispatch_queue_serial
    刀哥多线程之03GCD 常用代码
  • 原文地址:https://www.cnblogs.com/LightSmile/p/11055665.html
Copyright © 2011-2022 走看看