• Windows调试技巧&工具


    前言

    我们使用调试工具的时候,一般是要处理以下问题:

    1. crash
    2. anr application no response
    3. memory leak
    4. dead lock
    5. high cpu usage

    Windows平台上有很多对应的处理方案,不过现有的方案一般都有一个通病:一旦打开某些调试功能或者系统设置,调试目标的性能就会成倍下降。这一点有时候会对调试来带很大的干扰,比如出现目标程序UI线程卡顿,导致无法正常操作,或者没发现要调查的问题,反而是发现了一堆性能方面的问题。

    crash

    崩溃的情况可以从几个正交的维度来划分:是否必现,开发的机器上是否可以复现,是否抓到crash dump。

    一旦接手一个crash问题排查,可以按照下面的步骤来排查:

    1. 发现问题的机器上是否可以复现。可以复现的情况又分为:
      • 必现 : 这个情况是最简单了。
      • 高概率复现 :这个也比较简单。
      • 低概率复现,甚至只出现一次 :这个最棘手,作为开发最怕遇到这类问题。但是理论上讲,只要能发现一次就一定有一个必现的路径,只不过实践中碰到的概率低而已。套用一句话来描述就是:crash只有0次和无数次。
    2. 能复现的话,是否有复现路径。
      • 有的话,开发的机器上是否可以复现:那么直接用调试器来看就好了,这个最简单了,调试器就可以直接抓到crash点。
      • 开发的机器上不能复现的话,qa的机器上是否可以复现:可以通过远程调试的方法来处理。
      • 如果开发和测试的机器都不能复现的话,可能就需要联系客户了。这个要做好准备,这时候可以使用的方法就是:日志+dump。
        • 使用dump还要看是否能(尽量)跑debug版本,因为有时候release版本缺少一些符号信息,可能会出现即使复现了,但由于缺少符号,缺少active信息而导致无法进一步查问题。
        • 提一下,日志对于crash问题来说往往帮助不大,不过聊胜于无。
    3. 走到这一步,那就是说这个问题很可能不好复现,而且拿不到dump信息,那么这时候问题的解决就很可能没希望了。留下最后一个办法就是:耐心。排查代码,不论使用什么方法,在怀疑的点上加日志,做好准备抓dump,部署到目标环境,然后静候crash发生。

    对于上面的场景,其实概括起来处理方法就三种:

    1. 本地调试:一般是使用MSVC
    2. 远程调试:MSVC或者WinDbg都可以
    3. 抓dump分析:MSVC或者WinDbg都可以,生成dump的方式,可以在资源管理器里面找到目标进程,右键执行创建转储文件;或者使用MSVC/WinDbg的创建dump功能。

    anr

    首先,对于anr的问题要注意的一个问题是,一定要确定anr确实发生了,因为有时候可能是程序运行环境配置较差导致的,静置一会儿看看是否就没问题了。当然如果干脆就不允许出现anr,那么应该有两方面的考虑:给出最低环境配置和找出造成anr的问题代码(比较有追求的做法)。

    确定是anr问题了,那么接下来就要采取一些措施,比如动态抓dump看UI线程卡在哪里了。或者直接制造一个crash,也是得到dump,调查一下UI线程卡在哪里了。当然如果能上(本地或者远程)调试器的话是最好的了,anr的时候直接让程序停下来就可以在call stack中看到UI线程在干嘛。

    另外提一下,mac上有一个不错的工具,可以在活动监视器里面采样,从而直观地看到哪行代码有问题。Windows系统上就只能用msvc工具来查了,但毕竟并不是所有(测试/客户的)环境都有调试工具,不是很好查;同时这个问题还可能与high cpu usage或者dead lock有点关联。

    memory leak

    内存泄露问题,可以:

    dead lock

    发现死锁本身就是一个问题,比如非UI线程死锁,可能就没有UI线程卡死那么直观。可能是某些对该线程的同步调用卡住,或者异步调用没有结果返回才能发现。

    发现之后可以抓dump,或者上调试器。

    high cpu usage

    Windows上由于缺少一个对进程的各个线程采样统计的工具(我没发现),所以这个问题查起来没有那么直接。WinDbg上可以用!runaway来查CPU的使用时间,但是这个使用时间,我们无法指定开始统计和结束统计的时间点,也就是说真正耗时的操作可能会被很早运行的线程运行时长超过,导致我们定位不出来。

    Profile

    PC Profile工具

    手工复原堆栈

    参考 Manually Walking a Stack

    堆栈失效,可能是由于对无效地址的调用导致调试器丢失了返回地址的位置;或者您可能遇到了无法直接获取堆栈跟踪的堆栈指针;或者可能存在其他一些调试器问题。

    思路是:

    • 先从检查符号开始 x *! 命令
    • dd esp/rsp 查看栈指针附近的信息,找出可能是函数指针的值,这里要注意排除掉下面的值:
      • 一般整数比较小,dd的时候大部分位是0,这类值不太可能是函数地址
      • 英文字符的范围一般在 [20,7f] 区间
      • 指向栈上的指针变量大小一般与esp/rsp接近
      • 函数指针一般不太可能重复
      • 状态码通常以 ac (c00000d6) 开头
      • 最后,栈的地址应该位于模块的区间内,也就是在第一步x命令找到的模块的地址区间内
    • 这样得到一组函数指针备选集,然后逐个使用ln address查看对应符号,得到一组可能的函数调用符号
    • 然后通过反汇编指令u 函数指针备选集合中的每一个元素的前一个地址,看看得到的结果是否匹配上面那组符号,从而构建出调用堆栈来。

    GFlags

    Windows上好多调试功能都需要 gflags 这个工具。

    GFlags(全局标志编辑器)gflags.exe 启用和禁用高级调试、诊断和故障排除功能

    gflags官方有一个实例说明:GFlags Examples

  • 相关阅读:
    面试官:Java反射是什么?我回答不上来
    产品经理求职方法指南:面试通关
    C++左值/右值、左值引用&/右值引用&&、移动语义move、完美转发forward
    LeetCode50天刷题计划第二季(Day 23 — 重排链表(16.20- 17.00)
    模板学堂丨禅道业务数据分析大屏
    canvas画布绘制线条样式:粗细,圆角,拐角等
    Unity中集合的随机数(指定长度—List、Dictionary)
    Java面试题以及答案---Nginx
    IF:14+ “冒烟型”骨髓瘤的分子组成突显了导致多发性骨髓瘤的进化途径
    一年赚一百万的思路—别做大多数的傻瓜
  • 原文地址:https://blog.csdn.net/lgyxdn/article/details/125541885